initial commit

This commit is contained in:
Afif Hendrawan 2025-08-08 14:12:40 +07:00
commit 83474a2539
497 changed files with 66375 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/.idea
/.vscode
/node_modules

25
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,25 @@
image: ubuntu:22.04
stages:
- deploy
deploy_to_vps:
stage: deploy
before_script:
- apt-get update && apt-get install -y openssh-client curl # Install curl jika belum ada
script:
# Memuat private key yang disimpan di GitLab CI/CD variable
- eval "$(ssh-agent -s)"
- echo "$FRONT_PRIVATE_KEY" | sed 's/\r//g' | ssh-add -
# Menambahkan VPS ke known_hosts secara otomatis untuk menghindari konfirmasi manual
- mkdir -p ~/.ssh
- ssh-keyscan -H $VPS_HOST >> ~/.ssh/known_hosts
- ssh $FRONTEND_USER@$VPS_HOST "export PATH="/home/frontend/.nvm/versions/node/v20.16.0/bin:$PATH" && cd $FRONTEND_DIR && git checkout dev && git pull && npm i && npm run build && pm2 stop 0 && pm2 start 0"
only:
- dev # Jalankan hanya ketika ada perubahan di branch dev

173
README.md Normal file
View File

@ -0,0 +1,173 @@
# koperasi
## Getting started
# Wallet Service
## Configuration
Create database postgresql with name `wallet`
Open new terminal in `services/wallet` directory
Instalation dependencies
```shell
npm install
```
Copy .env.example to .env
```shell
cp .env.example .env
```
Set your database configuration in .env
generate secret key for `API_KEY` with command
```shell
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```
> Note:
> API_KEY Backend Service and Wallet Service must be the same
## Run project
Migration database
```shell
npm run migrate:fresh
```
Run project
```shell
npm run dev
```
# Smart Contract Service
## Configuration
Open new terminal in `services/smartcontract` directory
Instalation dependencies
```shell
npm install
```
## Run project
Run localhost network hardhat (network blockchain for development)
```shell
npx hardhat node
```
Open new terminal again in `services/smartcontract` directory
Deploy
```shell
npx hardhat ignition deploy ./ignition/modules/Deploy.ts --network localhost
```
# Backend Service
## Configuration
Create database postgresql with name `koperasi`
Open new terminal in `services/backend` directory
Instalation dependencies
```shell
npm install
```
Copy .env.example to .env
```shell
cp .env.example .env
```
Set your database configuration in .env
generate secret key for `JWT_SECRET` with command
```shell
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```
Set `contractABI.json` with contract abi after compile contract
set `PRIVATE_KEY=" "` with private key wallet blockchain
Set `CONTRACT_ADDRESS=" "` with contract address after deploy contract
## Run project
Migration database
```shell
npm run migrate:fresh
```
Seed fake data
```shell
npm run db:seed
```
Run project
```shell
npm run dev
```
# Frontend Service
## Configuration
Open new terminal in `services/frontend` directory
Instalation dependencies
```shell
npm install
```
Copy .env.example to .env
```shell
cp .env.example .env
```
set `API_BASE_URL` with backend service url
example: `API_BASE_URL=http://localhost:3000`
generate `SECRET_COOKIE_PASSWORD` with command
```shell
openssl rand -base64 32
```
## Run project
Run project
```shell
npm run dev
```
# Import Postman Collection
create new workspace in postman
extract `api-docs/postman-koperasi-blockchain.zip`
import postman collection and environment in directory `postman-koperasi-blockchain` to your postman

0
api-docs/README.md Normal file
View File

Binary file not shown.

3403
api-docs/swagger-output.json Normal file

File diff suppressed because it is too large Load Diff

4
env-init.sh Normal file
View File

@ -0,0 +1,4 @@
#!/bin/bash
# Your initialization script commands here
cd ./services/backend && cp .env.example .env

2
install.sh Normal file
View File

@ -0,0 +1,2 @@
cd ./services/frontend && npm install
cd ./services/backend && npm install

View File

@ -0,0 +1,22 @@
# PORT
PORT=3000
# WALLET SERVICE
WALLET_URL=http://localhost:3001
API_KEY=
# DB
DATABASE_URL=postgres://<username>:<password>@0.0.0.0:5432/<databasename>
DB_HOST=localhost
DB_PORT=5432
DB_USER=postgres
DB_PASSWORD=
DB_NAME=
# JWT
JWT_SECRET=
# SMART CONTRACT
API_URL="http://127.0.0.1:8545"
PRIVATE_KEY="..."
CONTRACT_ADDRESS="0x..."

36
services/backend/.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# code editor settings
/.idea
/.vscode
# dependencies
/node_modules
# dotenv environment variables file
.env
# build / generate output
dist
# drizzle migrations
/src/drizzle/migrations/
# user uploaded files
/assets/uploads/foto_diri/*
/assets/uploads/foto_ktp/*
/assets/uploads/foto_profile/*
/assets/uploads/dokumen_pendukung/*
/assets/uploads/dokumen_proyeksi/*
/assets/uploads/brosur_produk/*
/assets/uploads/tanda_tangan/*
/assets/uploads/tanda_tangan_admin/*
/assets/uploads/bukti_pembayaran/*
/assets/uploads/laporan_keuangan/*
/assets/uploads/laporan_mutasi/*
/assets/uploads/bukti_transfer/*
/assets/uploads/dokumen_prospektus/*
# not ignore empty directories when have .gitkeep file
!/**/.gitkeep
# auth baileys
/auth_baileys

21
services/backend/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Geshan Manandhar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,53 @@
# Backend Service
## Configuration
Create database postgresql with name `koperasi`
Go to `services/backend` directory
Instalation dependencies
```shell
npm install
```
Copy .env.example to .env
```shell
cp .env.example .env
```
Set your database configuration in .env
generate secret key for `JWT_SECRET` with command
```shell
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
```
Set `contractABI.json` with contract abi after compile contract
set `PRIVATE_KEY=" "` with private key wallet blockchain
Set `CONTRACT_ADDRESS=" "` with contract address after deploy contract
## Run project
Migration database
```shell
npm run migrate:fresh
```
Seed fake data
```shell
npm run db:seed
```
Run project
```shell
npm run dev
```

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,12 @@
import { defineConfig } from "drizzle-kit";
export default defineConfig({
dialect: "postgresql",
schema: "./src/drizzle/schema.ts",
out: "./src/drizzle/migrations",
dbCredentials: {
url: process.env.DATABASE_URL as string,
},
verbose: true,
strict: true,
});

6
services/backend/express.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
declare namespace Express {
interface Request {
user?: any;
query?: any;
}
}

4869
services/backend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,65 @@
{
"name": "expressjs-structure",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"dev": "tsx watch src/main.ts",
"build": "tsc",
"serve": "node dist/main.js",
"bot": "ts-node src/services/baileys.ts",
"db:generate": "drizzle-kit generate",
"db:migrate": "tsx src/drizzle/migrate.ts",
"db:drop": "psql -U postgres -d postgres -c \"DROP DATABASE IF EXISTS koperasi;\"",
"db:create": "psql -U postgres -d postgres -c \"CREATE DATABASE koperasi;\"",
"db:seed": "tsx src/drizzle/seed.ts",
"migrate:fresh": "rm -rf src/drizzle/migrations/* && npm run db:generate && npm run db:drop && npm run db:create && npm run db:migrate",
"rm:upload": "rm -rf assets/uploads/foto_diri/* && rm -rf assets/uploads/foto_ktp/* && rm -rf assets/uploads/brosur_produk/* && rm -rf assets/uploads/dokumen_pendukung/* && rm -rf assets/uploads/dokumen_prospektus/* && rm -rf assets/uploads/dokumen_proyeksi/* && rm -rf assets/uploads/tanda_tangan/* && rm -rf assets/uploads/bukti_pembayaran/* && rm -rf assets/uploads/laporan_keuangan/* && rm -rf assets/uploads/laporan_mutasi/* && rm -rf assets/uploads/tanda_tangan_admin/*"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/multer": "^1.4.11",
"@types/node": "^22.1.0",
"@types/node-cron": "^3.0.11",
"@types/nodemailer": "^6.4.15",
"drizzle-kit": "^0.23.2",
"express": "^4.19.2",
"ts-node": "^10.9.2",
"typescript": "^5.5.4"
},
"dependencies": {
"@faker-js/faker": "^8.4.1",
"@types/express-validator": "^3.0.0",
"@types/jsonwebtoken": "^9.0.6",
"@types/swagger-ui-express": "^4.1.6",
"@whiskeysockets/baileys": "^6.7.7",
"axios": "^1.7.8",
"bcrypt": "^5.1.1",
"bcryptjs": "^2.4.3",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"date-fns": "^3.6.0",
"dotenv": "^16.4.5",
"drizzle-orm": "^0.32.2",
"ethers": "^6.13.3",
"express-validator": "^7.1.0",
"idn-area-data": "^3.1.1",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"node-cron": "^3.0.3",
"nodemailer": "^6.9.14",
"pg": "^8.12.0",
"postgres": "^3.4.4",
"qrcode-terminal": "^0.12.0",
"swagger-autogen": "^2.23.7",
"swagger-ui-express": "^5.0.1",
"tsx": "^4.17.0",
"zod": "^3.23.8",
"zod-validation-error": "^3.3.1"
}
}

View File

@ -0,0 +1,56 @@
import { Request, Response } from "express";
import * as agreementLetterService from "../services/agreement-letter.js";
export const createAgreementLetterHandler = async (req: Request, res: Response) => {
try {
await agreementLetterService.createAgreementLetter(req.body);
return res.status(201).json({ message: "Agreement letter created successfully" });
} catch (error: any) {
if (error.message.includes("Project not found")) {
return res.status(404).json({ message: "Project not found" });
}
if (error.message.includes("User associated with the project not found")) {
return res.status(404).json({ message: "User associated with the project not found" });
}
if (error.message.includes("Admin user not found")) {
return res.status(404).json({ message: "Admin user not found" });
}
if (error.message.includes("Admin signature not found")) {
return res.status(404).json({ message: "Admin signature not found" });
}
console.error("Error creating agreement letter:", error);
return res.status(500).json({ message: "Failed to create agreement letter" });
}
};
export const getAllAgreementLetterHandler = async (req: Request, res: Response) => {
try {
const { search } = req.query;
const response = await agreementLetterService.getAllAgreementLetter(search as string | undefined);
return res.status(200).json({ data: response });
} catch (error: any) {
if (error.statusCode === 404) {
return res.status(404).json({ message: error.message });
}
console.error("Error fetching all agreements:", error);
return res.status(500).json({ message: "Failed to fetch agreement letters" });
}
};
export const getAgreementLetterByProjectIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const response = await agreementLetterService.getAgreementByProjectId(id);
return res.status(200).json({ data: response });
} catch (error: any) {
if (error.message.includes("No agreements found for this project ID")) {
return res.status(404).json({ message: "No agreements found for this project ID" });
}
console.error("Error fetching agreement by project ID:", error);
return res.status(500).json({ message: "Failed to fetch agreement letter by project ID" });
}
};

View File

@ -0,0 +1,63 @@
import { Request, Response, NextFunction } from "express";
import * as auth from "../services/auth.js";
import { generateToken, tokenBlacklist } from "../services/jwt.js";
import jwt from "jsonwebtoken";
export async function register(req: Request, res: Response, next: NextFunction): Promise<void> {
try {
const user = req.body;
const response = await auth.register(user);
if (response.success) {
res.status(201).json({ message: response.message });
} else {
res.status(response.statusCode).json({ message: response.message });
}
} catch (error) {
console.error(`Error while creating user`);
res.status(500).json({ message: "Internal Server Error" });
next(error);
}
}
export async function login(req: Request, res: Response): Promise<void> {
try {
const { no_hp, password } = req.body;
const user = await auth.login(no_hp, password);
if (user) {
const token = generateToken({ id: user.id, role: user.role, status: user.status });
res.status(200).json({ token });
}
} catch (error: any) {
if (error.statusCode) {
res.status(error.statusCode).json({ message: error.message });
} else {
res.status(500).json({ message: "Internal Server Error" });
}
}
}
export function logout(req: Request, res: Response) {
try {
const authHeader = req.headers.authorization;
const token = authHeader?.split(" ")[1];
if (!token) {
return res.status(401).json({ message: "No token provided" });
}
const decoded = jwt.decode(token) as jwt.JwtPayload;
if (!decoded || !decoded.exp) {
return res.status(400).json({ message: "Invalid token structure" });
}
tokenBlacklist[token] = decoded.exp * 1000;
res.status(200).json({ message: "Logout successful" });
} catch (error) {
console.error(error);
res.status(500).json({ message: "Something went wrong during logout" });
}
}

View File

@ -0,0 +1,23 @@
import { Request, Response } from "express";
import { qrCode, isConnected } from "../services/baileys.js";
export const getQRCodeHandler = async (req: Request, res: Response) => {
try {
if (isConnected) {
return res.status(200).json({ status: "SUCCESS", message: "WA terhubung" });
} else if (qrCode) {
return res.status(200).json({ status: "NOTCONNECTED", qr: qrCode });
} else {
return res.status(404).json({
status: "ERROR",
message: "QR code not available, please try again later.",
});
}
} catch (error) {
console.error("Error in getQRCodeHandler:", error);
return res.status(500).json({
status: "ERROR",
message: "Internal server error, please try again later.",
});
}
};

View File

@ -0,0 +1,32 @@
import { Request, Response } from "express";
import * as chartProjectService from "../services/chart-project.js";
export const getChartProjectByIdHandler = async (req: Request, res: Response) => {
try {
const chartProject = await chartProjectService.getChartProjectById(req.params.id);
if (!chartProject || chartProject.length === 0) {
return res.status(404).json({ message: "Chart Project not found" });
}
return res.status(200).json({ data: chartProject });
} catch (error) {
console.error("Error fetching chart project by ID:", error);
return res.status(500).json({ message: "Internal server error" });
}
};
export const getAllChartProjectByUserIdHandler = async (req: Request, res: Response) => {
try {
const chartProject = await chartProjectService.getAllChartProjectByUserId(req.user.id);
if (!chartProject || chartProject.length === 0) {
return res.status(404).json({ message: "No Chart Projects found for this user" });
}
return res.status(200).json({ data: chartProject });
} catch (error) {
console.error("Error fetching all chart projects by user ID:", error);
return res.status(500).json({ message: "Internal server error" });
}
};

View File

@ -0,0 +1,50 @@
import { Request, Response } from "express";
import * as chartTokenService from "../services/chart-token.js";
export const getLastChartTokenByUserIdandProjectIdHandler = async (req: Request, res: Response) => {
try {
const chartToken = await chartTokenService.getLastChartTokenByUserIdandProjectId(req.user.id, req.params.id);
if (!chartToken) {
return res.status(404).json({ message: "Chart Token not found" });
}
return res.status(200).json({ data: chartToken });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const getChartTokenByUserIdandProjectIdHandler = async (req: Request, res: Response) => {
try {
const chartToken = await chartTokenService.getChartTokenByUserIdandProjectId(req.user.id, req.params.id);
if (!chartToken || chartToken.length === 0) {
return res.status(404).json({ message: "Chart Token not found" });
}
return res.status(200).json({ data: chartToken });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const getAllChartTokenByUserIdHandler = async (req: Request, res: Response) => {
try {
const chartToken = await chartTokenService.getAllChartTokenByUserId(req.user.id);
if (!chartToken || chartToken.length === 0) {
return res.status(404).json({ message: "Chart Token not found" });
}
return res.status(200).json({ data: chartToken });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};

View File

@ -0,0 +1,15 @@
import { Request, Response } from "express";
import * as historyProjectWalletService from "../services/history-project-wallet.js";
export const getHistoryProjectWalletByProjectWalletIdHandler = async (req: Request, res: Response) => {
try {
const historyProjectWallet = await historyProjectWalletService.getHistoryProjectWalletByProjectWalletId(req.params.id);
return res.status(200).json({ data: historyProjectWallet });
} catch (error: any) {
if (error.statusCode) {
res.status(error.statusCode).json({ message: error.message });
} else {
res.status(500).json({ message: "Internal Server Error" });
}
}
};

View File

@ -0,0 +1,13 @@
import { Request, Response } from "express";
import * as historyProjectService from "../services/history-project.js";
export const getHistoryProjectByProjectIdHandler = async (req: Request, res: Response) => {
try {
const historyProject = await historyProjectService.getHistoryProjectByProjectId(req.params.id);
return res.status(200).json({ data: historyProject });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};

View File

@ -0,0 +1,20 @@
import { Request, Response } from "express";
import * as historyTokenService from "../services/history-token.js";
export const getHistoryTokenByUserIdandProjectIdHandler = async (req: Request, res: Response) => {
try {
const historyToken = await historyTokenService.getHistoryTokenByUserIdAndProjectId(req.user.id, req.params.id);
if (!historyToken || historyToken.length === 0) {
return res.status(404).json({ message: "History Token not found" });
}
return res.status(200).json({ data: historyToken });
} catch (error: any) {
if (error.statusCode) {
res.status(error.statusCode).json({ message: error.message });
} else {
res.status(500).json({ message: "Internal Server Error" });
}
}
};

View File

@ -0,0 +1,54 @@
import { Request, Response } from "express";
import * as mutationService from "../services/mutation.js";
export const createMutationHandler = async (req: Request, res: Response) => {
try {
const mutation = await mutationService.createMutation(req.body);
return res.status(201).json({ message: "Mutation created successfully", data: mutation });
} catch (error: any) {
if (error.statusCode) {
return res.status(error.statusCode).json({ message: error.message });
} else {
return res.status(500).json({ message: "Internal server error" });
}
}
};
export const getAllMutationHandler = async (req: Request, res: Response) => {
try {
const { search } = req.query;
const mutation = await mutationService.getAllMutation(search as string | undefined);
return res.status(200).json({ data: mutation });
} catch (error: any) {
if (error.statusCode) {
return res.status(error.statusCode).json({ message: error.message });
} else {
return res.status(500).json({ message: "Internal server error" });
}
}
};
export const getMutationByIdHandler = async (req: Request, res: Response) => {
try {
const mutation = await mutationService.getMutationById(req.params.id);
return res.status(200).json({ data: mutation });
} catch (error: any) {
if (error.statusCode) {
return res.status(error.statusCode).json({ message: error.message });
} else {
return res.status(500).json({ message: "Internal server error" });
}
}
};
export const getMutationByProjectIdHandler = async (req: Request, res: Response) => {
try {
const mutation = await mutationService.getMutationByProjectId(req.params.id);
return res.status(200).json({ data: mutation });
} catch (error: any) {
if (error.statusCode) {
return res.status(error.statusCode).json({ canUploadReport: error.canUploadReport, message: error.message });
} else {
return res.status(500).json({ message: "Internal server error" });
}
}
};

View File

@ -0,0 +1,70 @@
import { Request, Response } from "express";
import * as projectCategoryService from "../services/project-category.js";
export const createProjectCategoryHandler = async (req: Request, res: Response) => {
try {
const response = await projectCategoryService.createProjectCategory(req.body);
return res.status(201).json({ message: response.message });
} catch (error: any) {
if (error.message === "Category already exists") {
return res.status(409).json({ message: "Project Category already exists" });
} else if (error.message === "Category is required") {
return res.status(400).json({ message: "Category is required" });
}
console.error(error);
return res.status(500).json({ message: "Project Category failed to be created" });
}
};
export const getProjectCategoryHandler = async (req: Request, res: Response) => {
try {
const { search } = req.query;
const projects = await projectCategoryService.getAllProjectCategory(search as string | undefined);
return res.status(200).json({ data: projects });
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Failed to retrieve Project Categories" });
}
};
export const getProjectCategoryByIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const response = await projectCategoryService.getProjectCategoryById(id);
return res.status(200).json({ data: response });
} catch (error: any) {
if (error.message === "Project Category not found") {
return res.status(404).json({ message: "Project Category not found" });
}
console.error(error);
return res.status(500).json({ message: "Failed to retrieve Project Category" });
}
};
export const updateProjectCategoryByIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.body;
await projectCategoryService.updateProjectCategoryById(req.body, id);
return res.status(200).json({ message: "Project Category updated successfully" });
} catch (error: any) {
if (error.message === "Project Category not found") {
return res.status(404).json({ message: "Project Category not found" });
}
console.error(error);
return res.status(500).json({ message: "Failed to update Project Category" });
}
};
export const deleteProjectCategoryByIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.body;
await projectCategoryService.deleteProjectCategoryById(id);
return res.status(200).json({ message: "Project Category deleted successfully" });
} catch (error: any) {
if (error.message === "Project Category not found") {
return res.status(404).json({ message: "Project Category not found" });
}
console.error(error);
return res.status(500).json({ message: "Failed to delete Project Category" });
}
};

View File

@ -0,0 +1,63 @@
import { Request, Response } from "express";
import * as projectReportService from "../services/project-report.js";
export const createProjectReportHandler = async (req: Request, res: Response) => {
try {
const response = await projectReportService.createProjectReport(req.body);
return res.status(201).json({ message: response.message });
} catch (error: any) {
if (error.message === "Project not found") {
return res.status(404).json({ message: "Project not found" });
} else if (error.message.includes("You can upload the report after")) {
return res.status(400).json({ message: error.message });
}
console.error(error);
return res.status(500).json({ message: "Project report failed to be created" });
}
};
export const getAllProjectReportHandler = async (req: Request, res: Response) => {
try {
const { search } = req.query;
const response = await projectReportService.getAllProjectReport(search as string | undefined);
if (!response || response.length === 0) {
return res.status(404).json({ message: "Project report not found" });
}
return res.status(200).json({ data: response });
} catch (error) {
console.error("Error retrieving project reports:", error);
return res.status(500).json({ message: "Failed to retrieve project reports" });
}
};
export const getProjectReportHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const response = await projectReportService.getProjectReportById(id);
if (!response || response.length === 0) {
return res.status(404).json({ message: "Project report not found" });
}
return res.status(200).json({ data: response });
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Failed to retrieve project report" });
}
};
export const getProjectReportByProjectIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { jenis } = req.query;
const response = await projectReportService.getProjectReportByProjectId(id, jenis as string | undefined);
if (!response.projectReport || response.projectReport.length === 0) {
return res.status(404).json({ message: "Project report not found" });
}
return res.status(200).json({ data: response });
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Failed to retrieve project report" });
}
};

View File

@ -0,0 +1,124 @@
import * as projectTokenService from "../services/project-token.js";
import { Request, Response } from "express";
export const getTokenByIdProjectHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const response = await projectTokenService.getTokenByIdProject(id);
if (!response || response.length === 0) {
return res.status(404).json({ message: "Tokens not found for the given project ID" });
}
return res.status(200).json({ data: response });
} catch (error) {
console.error("Error in getTokenByIdProjectHandler:", error);
return res.status(500).json({ message: "Internal server error while retrieving tokens" });
}
};
export const getTokenProjectByUserHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const id_user = req.user.id;
const response = await projectTokenService.getTokenProjectByUser(id_user, id);
if (!response || response.length === 0) {
return res.status(404).json({ message: "Tokens not found for the user and project" });
}
return res.status(200).json({ data: response });
} catch (error) {
console.error("Error in getTokenProjectByUserHandler:", error);
return res.status(500).json({ message: "Internal server error while retrieving tokens for the user and project" });
}
};
export const getTotalTokenUsageByIdProjectHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const response = await projectTokenService.getTotalTokenTerbeliByIdProject(id);
if (!response || response.length === 0) {
return res.status(404).json({ message: "Tokens not found for the user and project" });
}
return res.status(200).json({ data: response });
} catch (error) {
console.error("Error in getTokenProjectByUserHandler:", error);
return res.status(500).json({ message: "Internal server error while retrieving tokens for the user and project" });
}
};
export const buyTokenProjectHandler = async (req: Request, res: Response) => {
try {
const id_user = req.user.id;
const { id_projek, jumlah_token } = req.body;
const buyToken = await projectTokenService.buyTokenProject(id_user, id_projek, jumlah_token);
return res.status(201).json({ message: "Token purchased successfully", data: buyToken });
} catch (error: any) {
if (error.message.includes("Available tokens not found")) {
return res.status(404).json({ message: "No available tokens found for this project" });
} else if (error.message.includes("User not found")) {
return res.status(404).json({ message: "User not found" });
} else if (error.message.includes("Project not found")) {
return res.status(404).json({ message: "Project not found" });
} else if (error.message.includes("Insufficient tokens available for purchase")) {
return res.status(400).json({ message: "Insufficient tokens available for purchase" });
} else if (error.message.includes("Insufficient balance")) {
return res.status(400).json({ message: "Insufficient balance in user's wallet" });
}
console.error("Error in buyTokenProjectHandler:", error);
return res.status(500).json({ message: "Internal server error during token purchase" });
}
};
export const getAllTokenHandler = async (req: Request, res: Response) => {
try {
const { search, ...filter } = req.query;
const response = await projectTokenService.getAllToken();
if (!response || response.length === 0) {
return res.status(404).json({ message: "No tokens found" });
}
return res.status(200).json({ data: response });
} catch (error) {
console.error("Error in getAllTokenHandler:", error);
return res.status(500).json({ message: "Internal server error while retrieving all tokens" });
}
};
export const getTotalTokenHandler = async (req: Request, res: Response) => {
try {
const response = await projectTokenService.getTotalToken();
if (!response || response === "0") {
return res.status(404).json({ message: "Total token not found" });
}
return res.status(200).json({ data: response });
} catch (error) {
console.error("Error in getTotalTokenHandler:", error);
return res.status(500).json({ message: "Internal server error while retrieving total tokens" });
}
};
export const tokenUsageDetailsByIdUserHandler = async (req: Request, res: Response) => {
try {
const { id } = req.user;
const { search, ...filter } = req.query;
const response = await projectTokenService.tokenUsageDetailsByIdUser(id);
if (!response || response.length === 0) {
return res.status(404).json({ message: "No token usage details found for the user" });
}
return res.status(200).json({ data: response });
} catch (error) {
console.error("Error in tokenUsageDetailsByIdUserHandler:", error);
return res.status(500).json({ message: "Internal server error while retrieving token usage details" });
}
};
export const getTotalTokenRupiahByUserHandler = async (req: Request, res: Response) => {
try {
const { id } = req.user;
const response = await projectTokenService.getTotalTokenRupiahByUser(id);
if (!response) {
return res.status(404).json({ message: "Total token rupiah not found" });
}
return res.status(200).json({ data: response });
} catch (error) {
console.error("Error in getTotalTokenRupiahByUserHandler:", error);
return res.status(500).json({ message: "Internal server error while retrieving total token rupiah" });
}
}

View File

@ -0,0 +1,42 @@
import { Request, Response } from "express";
import * as projectWalletService from "../services/project-wallet.js";
export const getProjectWalletHandler = async (req: Request, res: Response) => {
try {
const { search } = req.query;
const wallet = await projectWalletService.getAllProjectWallet(search as string);
return res.status(200).json({ data: wallet });
} catch (error: any) {
if (error.statusCode) {
return res.status(error.statusCode).json({ message: error.message });
} else {
return res.status(500).json({ message: "Internal server error" });
}
}
};
export const getProjectWalletByProjectIdHandler = async (req: Request, res: Response) => {
try {
const wallet = await projectWalletService.getProjectWalletByProjectId(req.params.id);
return res.status(200).json({ data: wallet });
} catch (error: any) {
if (error.statusCode) {
res.status(error.statusCode).json({ message: error.message });
} else {
res.status(500).json({ message: "Internal Server Error" });
}
}
};
export const transferSaldoProjectHandler = async (req: Request, res: Response) => {
try {
await projectWalletService.transferSaldoProject(req.body);
return res.status(201).json({ message: "Balance transferred successfully" });
} catch (error: any) {
if (error.statusCode) {
res.status(error.statusCode).json({ message: error.message });
} else {
res.status(500).json({ message: "Internal Server Error" });
}
}
};

View File

@ -0,0 +1,247 @@
import { Request, Response } from "express";
import * as projectService from "../services/project.js";
import path from "path";
export const countProjectHandler = async (req: Request, res: Response) => {
try {
const response = await projectService.countProject();
return res.status(200).json({ data: response });
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Failed to retrieve projects" });
}
};
export const createProjectHandler = async (req: Request, res: Response) => {
try {
const project = await projectService.createProject(req.user.id, req.body);
return res.json(project);
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const getAllProjectHandler = async (req: Request, res: Response) => {
try {
const { search, ...filter } = req.query;
const projects = await projectService.getAllProject(search as string | undefined, filter as { [key: string]: string | undefined });
return res.status(200).json({ data: projects });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const getProjectByIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const response = await projectService.getProjectById(id);
if (!response) {
return res.status(404).json({ message: "Project not found" });
}
return res.status(200).json({ data: response });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const getProjectByUserIdHandler = async (req: Request, res: Response) => {
try {
const { search, ...filter } = req.query;
const response = await projectService.getProjectByUserId(req.user.id, search as string | undefined, filter as { [key: string]: string | undefined });
if (!response || response.length === 0) {
return res.status(404).json({ message: "Project not found" });
}
return res.status(200).json({ data: response });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const getUserHaveTokenInProjectHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const response = await projectService.getUserHaveTokenInProject(id);
return res.status(200).json({ data: response });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const acceptProjectHandler = async (req: Request, res: Response) => {
try {
const { id } = req.body;
await projectService.acceptProjectById(id);
return res.status(200).json({ message: "Project accepted" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const rejectProjectHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { keterangan } = req.body;
await projectService.rejectProjectById(id, keterangan);
return res.status(200).json({ message: "Project rejected" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const reviseProjectHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { keterangan } = req.body;
await projectService.reviseProjectById(id, keterangan);
return res.status(200).json({ message: "Project revised" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const getKeteranganReviseProjectByIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const data = await projectService.getKeteranganReviseProjectById(id);
return res.status(200).json({ data });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const updateProjectHandler = async (req: Request, res: Response) => {
try {
const project = await projectService.updateProjectById(req.user.id, req.body);
return res.status(201).json(project);
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const approveProjectHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const detail = req.body;
await projectService.approveProjectById(id, detail);
return res.status(200).json({ message: "Project approved" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const deleteProjectHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
await projectService.deleteProjectById(id);
return res.status(200).json({ message: "Project deleted" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const publishProjectHandler = async (req: Request, res: Response) => {
try {
await projectService.publishProjectById(req.body);
console.log(req.body);
return res.status(200).json({ message: "Project published" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const checkProjectFundingOpenedHandler = async (req: Request, res: Response) => {
try {
const response = await projectService.checkProjectFundingOpened();
return res.status(200).json({ data: response });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const completingProjectHandler = async (req: Request, res: Response) => {
try {
const { id } = req.body;
const project = await projectService.completingProjectById(id);
return res.status(200).json({ message: "Project completed" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const totalProfitHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const totalProfit = await projectService.totalProfit(id);
if (!totalProfit) {
return res.status(404).json({ message: "Total profit not found" });
}
return res.status(200).json({ total: totalProfit });
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Failed to retrieve total profit" });
}
};
export const shareProfitHandler = async (req: Request, res: Response) => {
try {
const { id } = req.body;
const project = await projectService.shareProfit(id);
return res.status(200).json(project);
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const getDokumenProspektusByIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const dokumenPath = await projectService.getDokumenProspektusById(id);
if (!dokumenPath) {
return res.status(404).json({ message: "Dokumen prospektus not found" });
}
const filePath = path.resolve(__dirname, "../../assets/", dokumenPath);
return res.sendFile(filePath);
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
return res.status(statusCode).json({ message });
}
};

View File

@ -0,0 +1,272 @@
import {
accSimpananWajib,
accTopup,
accWithdrawSaldo,
getAllTopUp,
getWithdrawSaldo,
payMember,
paySimpananWajib,
payTopup,
withdrawSaldo,
getTopupById,
getBagianPemilikPelaksana,
payBagianPemilikPelaksana,
getTotalWalletSimpananPokokByUserId,
getTotalWalletSimpananWajibByUserId,
getKasKoperasi,
formatGetTopupByUserId,
formatGetAllTopup,
} from "../services/topup.js";
import { Request, Response } from "express";
import axios from "axios";
import { walletServiceUrl } from "../main.js";
import { getWalletSaldoByUserId, updateWalletById } from "../services/wallet.js";
export const getAllTopupHandler = async (req: Request, res: Response) => {
try {
const { search } = req.query;
const topups = await formatGetAllTopup(search as string | undefined);
return res.status(200).json({ message: "Topups found", data: topups });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to retrieve topups";
res.status(statusCode).json({ message });
}
};
export const getTopupByIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const topup = await getTopupById(id);
return res.status(200).json({ message: "Topup found", data: topup });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to retrieve topup";
res.status(statusCode).json({ message });
}
};
export const getTopupByUserIdHandler = async (req: Request, res: Response) => {
try {
const userId = req.user.id;
const data = await formatGetTopupByUserId(userId);
return res.status(200).json({
message: "Topups found",
data,
});
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "An error occurred while fetching topups";
return res.status(statusCode).json({ message });
}
};
export const getTotalWalletSimpananPokokByUserIdHandler = async (req: Request, res: Response) => {
try {
const userId = req.user.id;
const total = await getTotalWalletSimpananPokokByUserId(userId);
if (total === 0) {
return res.status(404).json({ message: "No Wallet simpanan pokok found for the user" });
}
return res.status(200).json({ message: "Total wallet simpanan pokok found", total });
} catch (error: any) {
console.error("Error calculating total Wallet simpanan pokok:", error);
const statusCode = error.statusCode || 500;
const message = error.message || "An error occurred while calculating total simpanan pokok";
res.status(statusCode).json({ message });
}
};
export const getTotalWalletSimpananWajibByUserIdHandler = async (req: Request, res: Response) => {
try {
const userId = req.user.id;
const total = await getTotalWalletSimpananWajibByUserId(userId);
if (total === 0) {
return res.status(404).json({ message: "No Wallet simpanan wajib found for the user" });
}
return res.status(200).json({ message: "Total Wallet simpanan wajib found", total });
} catch (error: any) {
console.error("Error calculating total Wallet simpanan wajib:", error);
const statusCode = error.statusCode || 500;
const message = error.message || "An error occurred while calculating total simpanan wajib";
res.status(statusCode).json({ message });
}
};
export const getTotalWalletSaldoByUserIdHandler = async (req: Request, res: Response) => {
try {
const response = await axios.get(`${walletServiceUrl}/wallet/total/${req.user.id}`);
return res.status(200).json(response.data);
} catch (error: any) {
console.error("Error calculating total Wallet saldo:", error);
if (error.response && error.response.status === 404) {
return res.status(404).json({ message: "Wallet saldo not found" });
}
const statusCode = error.response?.status || 500;
const message = error.message || "An error occurred while calculating total Wallet saldo";
return res.status(statusCode).json({ message });
}
};
export const getWalletSaldoByUserIdHandler = async (req: Request, res: Response) => {
try {
const response = await getWalletSaldoByUserId(req.params.id);
return res.status(200).json(response.data);
} catch (error: any) {
console.error("Error get Wallet saldo:", error);
if (error.response && error.response.status === 404) {
return res.status(404).json({ message: "Wallet saldo not found" });
}
const statusCode = error.response?.status || 500;
const message = error.message || "An error occurred while calculating Wallet saldo";
return res.status(statusCode).json({ message });
}
};
export const getKasKoperasiHandler = async (req: Request, res: Response) => {
try {
const total = await getKasKoperasi();
if (total === 0) {
return res.status(404).json({ message: "No Wallet saldo found for the user" });
}
return res.status(200).json({ message: "Kas Koperasi found", total });
} catch (error: any) {
console.error("Error calculating total Wallet saldo:", error);
const statusCode = error.statusCode || 500;
const message = error.message || "An error occurred while calculating total Wallet saldo";
res.status(statusCode).json({ message });
}
};
export const getWithdrawSaldoHandler = async (req: Request, res: Response) => {
try {
const topup = await getWithdrawSaldo();
return res.status(200).json({ message: "Withdraws found", data: topup });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to retrieve withdraws";
res.status(statusCode).json({ message });
}
};
export const payMemberHandler = async (req: Request, res: Response) => {
try {
await payMember(req.user.id, req.body);
return res.status(201).json({ message: "Topup created, awaiting payment confirmation" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to create topup";
res.status(statusCode).json({ message });
}
};
export const payTopupHandler = async (req: Request, res: Response) => {
try {
await payTopup(req.user.id, req.body);
return res.status(201).json({ message: "Topup created, awaiting payment confirmation" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to create topup";
res.status(statusCode).json({ message });
}
};
export const accTopupHandler = async (req: Request, res: Response) => {
try {
await accTopup(req.body.id);
return res.status(200).json({ message: "Topup has been approved" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to approve topup";
res.status(statusCode).json({ message });
}
};
export const withdrawSaldoHandler = async (req: Request, res: Response) => {
try {
await withdrawSaldo(req.user.id, req.body);
return res.status(201).json({ message: "Withdraw request created, awaiting approval" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to create withdraw request";
res.status(statusCode).json({ message });
}
};
export const accWithdrawSaldoHandler = async (req: Request, res: Response) => {
try {
await accWithdrawSaldo(req.body.id, req.body);
return res.status(200).json({ message: "Withdraw request has been approved" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to approve withdraw request";
res.status(statusCode).json({ message });
}
};
export const paySimpananWajibHandler = async (req: Request, res: Response) => {
try {
await paySimpananWajib(req.user.id, req.body);
return res.status(201).json({ message: "Simpanan Wajib created, awaiting payment confirmation" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to create simpanan wajib";
res.status(statusCode).json({ message });
}
};
export const accSimpananWajibHandler = async (req: Request, res: Response) => {
try {
await accSimpananWajib(req.body.id);
return res.status(200).json({ message: "Simpanan Wajib has been approved" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to approve simpanan wajib";
res.status(statusCode).json({ message });
}
};
export const getBagianPemilikPelaksanaHandler = async (req: Request, res: Response) => {
try {
const { search } = req.query;
const topup = await getBagianPemilikPelaksana(search as string | undefined);
return res.status(200).json({ message: "Topups found", data: topup });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to retrieve topups";
res.status(statusCode).json({ message });
}
};
export const payBagianPemilikPelaksanaHandler = async (req: Request, res: Response) => {
try {
await payBagianPemilikPelaksana(req.body);
return res.status(201).json({ message: "Pembayaran pemilik / pelaksana telah dikonfirmasi" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to confirm payment";
res.status(statusCode).json({ message });
}
};
export const updateWalletByIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const wallet = req.body;
await updateWalletById(wallet, id);
return res.status(200).json({ message: "Wallet updated" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};

View File

@ -0,0 +1,57 @@
import { Request, Response } from "express";
import * as transactionService from "../services/transaction.js";
export async function getAllTransactionHandler(req: Request, res: Response) {
try {
const { search } = req.query;
const transactions = await transactionService.getAllTransaction(search as string);
if (!transactions.length) {
return res.status(404).json({
message: "No transactions found",
});
}
return res.status(200).json({
data: transactions,
});
} catch (error) {
console.error("Error in getAllTransactionHandler:", error);
return res.status(500).json({ message: "Failed to retrieve transactions" });
}
}
export const getTransactionByUserIdHandler = async (req: Request, res: Response) => {
try {
const transaction = await transactionService.getTransactionByUserId(req.user.id);
if (!transaction.length) {
return res.status(404).json({ message: "No transactions found for this user" });
}
return res.status(200).json({ data: transaction });
} catch (error: any) {
console.error("Error in getTransactionByUserIdHandler:", error);
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to retrieve transactions";
return res.status(statusCode).json({ message });
}
};
export const getTransactionByProjectIdHandler = async (req: Request, res: Response) => {
try {
const transaction = await transactionService.getTransactionByProjectId(req.params.id);
if (!transaction.length) {
return res.status(404).json({ message: "No transactions found for this project" });
}
return res.status(200).json({ data: transaction });
} catch (error: any) {
console.error("Error in getTransactionByProjectIdHandler:", error);
const statusCode = error.statusCode || 500;
const message = error.message || "Failed to retrieve transactions";
return res.status(statusCode).json({ message });
}
};

View File

@ -0,0 +1,148 @@
import { Request, Response } from "express";
import * as userService from "../services/user.js";
export const countUserHandler = async (req: Request, res: Response) => {
try {
const response = await userService.countUser();
return res.status(200).json({ data: response });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const getAllUserHandler = async (req: Request, res: Response) => {
try {
const { search, ...filter } = req.query;
const response = await userService.getAllUser(search as string | undefined, filter as { [key: string]: string | undefined });
return res.status(200).json({ data: response });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const getUserByIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const response = await userService.getUserById(id);
return res.status(200).json({ data: response });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const updateUserByIdHandler = async (req: Request, res: Response) => {
try {
const userId = req.params.id;
const user = req.user;
if (user.role === "ADMIN" || user.id === userId) {
await userService.updateUserById(req.body, userId);
return res.status(200).json({ message: "User updated successfully" });
} else {
return res.status(403).json({ message: "You are not authorized to update this user" });
}
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const deleteUserByIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
await userService.deleteUserById(id);
return res.status(200).json({ message: "User deleted successfully" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const rejectUserByIdHandler = async (req: Request, res: Response) => {
try {
const { id } = req.params;
const { message } = req.body;
await userService.rejectUserById(id, message);
return res.status(200).json({ message: "User rejected successfully" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const sendOtpHandler = async (req: Request, res: Response) => {
try {
const { id } = req.body;
await userService.sendOtp(id);
return res.status(200).json({ message: "OTP sent successfully" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const verifyOtpHandler = async (req: Request, res: Response) => {
try {
const id = req.user.id;
const { otp } = req.body;
await userService.verifyOtp(id, otp);
return res.status(200).json({ message: "OTP verified successfully" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const getPhoneNumberAdminHandler = async (req: Request, res: Response) => {
try {
const response = await userService.getPhoneNumberAdmin();
return res.status(200).json({
data: response.no_hp,
});
} catch (error: any) {
console.error("Error in getPhoneNumberAdminHandler:", error);
const statusCode = error.statusCode || 500;
return res.status(statusCode).json({
message: error.message || "Internal server error",
});
}
};
export const upgradeUserToPlatinumHandler = async (req: Request, res: Response) => {
try {
const filePath = req.body.bukti_pembayaran;
await userService.upgradeUserToPlatinum(req.user.id, req.body.nominal, req.body, filePath);
return res.status(201).json({
message: "User upgrade request created, awaiting approval"
});
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};
export const accUpgradeUserToPlatinumHandler = async (req: Request, res: Response) => {
try {
await userService.accUpgradeUserToPlatinum(req.body.id);
return res.status(200).json({ message: "User upgrade request approved" });
} catch (error: any) {
const statusCode = error.statusCode || 500;
const message = error.message || "Internal Server Error";
res.status(statusCode).json({ message });
}
};

View File

@ -0,0 +1,84 @@
import { Request, Response } from "express";
export const getProvincesHandler = async (req: Request, res: Response) => {
try {
const IdnArea = await import("idn-area-data");
const provinces = await IdnArea.getProvinces();
return res.status(200).json(provinces);
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Error retrieving provinces" });
}
};
export const getRegenciesHandler = async (req: Request, res: Response) => {
try {
const IdnArea = await import("idn-area-data");
const { province_code } = req.params;
if (!province_code) {
return res.status(400).json({ message: "Province code is required" });
}
const allRegencies = await IdnArea.getRegencies();
const filteredRegencies = allRegencies.filter(
(regency: any) => regency.province_code === province_code
);
if (filteredRegencies.length === 0) {
return res.status(404).json({ message: "No regencies found for this province code" });
}
return res.status(200).json(filteredRegencies);
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Error retrieving regencies" });
}
};
export const getDistrictHandler = async (req: Request, res: Response) => {
try {
const IdnArea = await import("idn-area-data");
const { regency_code } = req.params;
if (!regency_code) {
return res.status(400).json({ message: "Regency code is required" });
}
const allDistricts = await IdnArea.getDistricts();
const filteredDistrict = allDistricts.filter(
(district: any) => district.regency_code === regency_code
);
if (filteredDistrict.length === 0) {
return res.status(404).json({ message: "No districts found for this regency code" });
}
return res.status(200).json(filteredDistrict);
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Error retrieving districts" });
}
};
export const getVillagesHandler = async (req: Request, res: Response) => {
try {
const IdnArea = await import("idn-area-data");
const { district_code } = req.params;
if (!district_code) {
return res.status(400).json({ message: "District code is required" });
}
const allVillages = await IdnArea.getVillages();
const filteredVillages = allVillages.filter(
(village: any) => village.district_code === district_code
);
if (filteredVillages.length === 0) {
return res.status(404).json({ message: "No villages found for this district code" });
}
return res.status(200).json(filteredVillages);
} catch (error) {
console.error(error);
return res.status(500).json({ message: "Error retrieving villages" });
}
};

View File

@ -0,0 +1,15 @@
// src/config/db.ts
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import 'dotenv/config';
// Konfigurasi koneksi ke database
export const connection = postgres({
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT || '5432'),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
});
export const db = drizzle(connection);

View File

@ -0,0 +1,16 @@
import "dotenv/config"
import { drizzle } from 'drizzle-orm/postgres-js';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
import postgres from 'postgres';
const migrationClient = postgres(process.env.DATABASE_URL as string, { max: 1 });
async function main() {
await migrate(drizzle(migrationClient), {
migrationsFolder: './src/drizzle/migrations',
})
await migrationClient.end();
}
main()

View File

@ -0,0 +1,270 @@
import { relations } from "drizzle-orm";
import { pgTable, varchar, uuid, integer, pgEnum, timestamp, text, date } from "drizzle-orm/pg-core";
export const UserRole = pgEnum("user_role", ["ADMIN", "BASIC", "PLATINUM"]);
export const UserStatus = pgEnum("user_status", ["AKTIF", "TIDAK AKTIF", "DITOLAK", "MENUNGGU KONFIRMASI", "OTP TERKIRIM"]);
export const ReportProgress = pgEnum("report_progress", ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"]);
export const StatusProject = pgEnum("status_project", ["DRAFT", "PROSES VERIFIKASI", "REVISI", "APPROVAL", "TTD KONTRAK", "PENDANAAN DIBUKA", "BERJALAN", "DIBATALKAN", "DITOLAK", "SELESAI", "BERJALAN SIKLUS 2", "BERJALAN SIKLUS 3", "BERJALAN SIKLUS 4", "BERJALAN SIKLUS 5", "BERJALAN SIKLUS 6", "BERJALAN SIKLUS 7", "BERJALAN SIKLUS 8", "BERJALAN SIKLUS 9", "BERJALAN SIKLUS 10", "BERJALAN SIKLUS 11", "BERJALAN SIKLUS 12", "BERJALAN SIKLUS 13", "BERJALAN SIKLUS 14", "BERJALAN SIKLUS 15", "BERJALAN SIKLUS 16", "BERJALAN SIKLUS 17", "BERJALAN SIKLUS 18", "BERJALAN SIKLUS 19", "BERJALAN SIKLUS 20"]);
export const StatusHistoryProject = pgEnum("status_history", ["SUCCESS", "FAILED", "PENDING"]);
export const JenisWallet = pgEnum("jenis_wallet", ["SIMPANAN WAJIB", "SIMPANAN POKOK", "SALDO", "KAS KOPERASI"]);
export const JenisTopup = pgEnum("jenis_topup", ["SIMPANAN WAJIB", "SIMPANAN POKOK", "TOPUP SALDO", "PENARIKAN SALDO", "UPGRADE USER", "PELAKSANA", "PEMILIK"]);
export const JenisLaporan = pgEnum("jenis_laporan", ["UNTUNG", "RUGI"]);
export const StatusPembayaran = pgEnum("status", ["SUKSES", "GAGAL", "MENUNGGU KONFIRMASI"]);
export const UserTable = pgTable("user", {
id: uuid("id").primaryKey().defaultRandom(),
nama: varchar("nama", { length: 255 }).notNull(),
no_hp: varchar("no_hp").notNull().unique(),
role: UserRole("user_role"),
status: UserStatus("user_status").default("TIDAK AKTIF").notNull(),
password: varchar("password").notNull(),
tempat_lahir: varchar("tempat_lahir").notNull(),
tanggal_lahir: date("tanggal_lahir").notNull(),
provinsi: varchar("provinsi").notNull(),
kota: varchar("kota").notNull(),
kecamatan: varchar("kecamatan").notNull(),
alamat: varchar("alamat").notNull(),
nik: varchar("nik").notNull(),
foto_profile: text("foto_profile"),
foto_diri: text("foto_diri").notNull(),
foto_ktp: text("foto_ktp").notNull(),
created_at: timestamp("created_at").notNull().defaultNow(),
updated_at: timestamp("updated_at"),
otp: varchar("otp"),
});
export const ProjectCategoryTable = pgTable("project_category", {
id: uuid("id").primaryKey().defaultRandom(),
kategori: varchar("kategori", { length: 255 }).notNull(),
created_at: timestamp("created_at").notNull().defaultNow(),
updated_at: timestamp("updated_at"),
});
export const ProjectTable = pgTable("project", {
id: uuid("id").primaryKey().defaultRandom(),
id_user: uuid("id_user")
.notNull()
.references(() => UserTable.id),
id_kategori: uuid("id_kategori")
.notNull()
.references(() => ProjectCategoryTable.id),
judul: varchar("judul", { length: 255 }).notNull(),
deskripsi: text("deskripsi").notNull(),
nominal: integer("nominal").notNull(),
asset_jaminan: varchar("asset_jaminan").notNull(),
nilai_jaminan: integer("nilai_jaminan").notNull(),
lokasi_usaha: varchar("lokasi_usaha").notNull(),
detail_lokasi: text("detail_lokasi").notNull(),
brosur_produk: text("brosur_produk"),
pendapatan_perbulan: integer("pendapatan_perbulan").notNull(),
pengeluaran_perbulan: integer("pengeluaran_perbulan").notNull(),
report_progress: ReportProgress("report_progress"),
dokumen_proyeksi: text("dokumen_proyeksi").notNull(),
status: StatusProject("status_project").notNull(),
nominal_disetujui: integer("nominal_disetujui"),
harga_per_unit: integer("harga_per_unit"),
jumlah_koin: integer("jumlah_koin"),
minimal_pembelian: integer("minimal_pembelian"),
maksimal_pembelian: integer("maksimal_pembelian"),
mulai_penggalangan_dana: date("mulai_penggalangan_dana"),
selesai_penggalangan_dana: date("selesai_penggalangan_dana"),
dokumen_prospektus: text("dokumen_prospektus"),
limit_siklus: integer("limit_siklus"),
created_at: timestamp("created_at").notNull().defaultNow(),
updated_at: timestamp("updated_at"),
});
export const SupportDocumentTable = pgTable("support_document", {
id: uuid("id").primaryKey().defaultRandom(),
id_projek: uuid("id_projek")
.notNull()
.references(() => ProjectTable.id),
dokumen: text("dokumen").notNull(),
created_at: timestamp("created_at").notNull().defaultNow(),
updated_at: timestamp("updated_at"),
});
export const ProjectReportTable = pgTable("project_report", {
id: uuid("id").primaryKey().defaultRandom(),
id_projek: uuid("id_projek")
.notNull()
.references(() => ProjectTable.id),
judul: varchar("judul", { length: 255 }).notNull(),
jenis_laporan: JenisLaporan("jenis_laporan").notNull(),
modal: integer("modal").notNull(),
nominal: integer("nominal").notNull(),
// laporan: text("laporan").notNull(),
created_at: timestamp("created_at").defaultNow(),
updated_at: timestamp("updated_at"),
});
export const ProjectMutationReportTable = pgTable("project_mutation_report", {
id: uuid("id").primaryKey().defaultRandom(),
id_projek: uuid("id_projek")
.notNull()
.references(() => ProjectTable.id),
judul: varchar("judul", { length: 255 }).notNull(),
pemasukan: integer("pemasukan"),
pengeluaran: integer("pengeluaran"),
laporan: text("laporan").notNull(),
created_at: timestamp("created_at").notNull().defaultNow(),
updated_at: timestamp("updated_at"),
});
export const HistoryProjectTable = pgTable("history_project", {
id: uuid("id").primaryKey().defaultRandom(),
id_projek: uuid("id_projek")
.notNull()
.references(() => ProjectTable.id),
history: text("history").notNull(),
keterangan: text("keterangan"),
status: StatusHistoryProject("status_project").notNull(),
created_at: timestamp("created_at").notNull().defaultNow(),
updated_at: timestamp("updated_at"),
});
export const WalletTable = pgTable("wallet", {
id: uuid("id").primaryKey().defaultRandom(),
id_user: uuid("id_user")
.notNull()
.references(() => UserTable.id),
jenis_wallet: JenisWallet("jenis_wallet").notNull(),
saldo: integer("saldo").notNull().default(0),
created_at: timestamp("created_at").notNull().defaultNow(),
updated_at: timestamp("updated_at"),
});
export const TopupTable = pgTable("topup", {
id: uuid("id").primaryKey().defaultRandom(),
id_wallet: uuid("id_wallet")
.notNull()
.references(() => WalletTable.id),
nama: varchar("nama").notNull(),
nama_bank: varchar("nama_bank"),
no_rekening: varchar("no_rekening"),
nama_pemilik_rekening: varchar("nama_pemilik_rekening"),
nominal: integer("nominal").notNull(),
jenis: JenisTopup("jenis_topup").notNull(),
bukti_pembayaran: text("bukti_pembayaran"),
status: StatusPembayaran("status").default("MENUNGGU KONFIRMASI"),
created_at: timestamp("created_at").notNull().defaultNow(),
updated_at: timestamp("updated_at"),
});
export const SignatureAdminTable = pgTable("signature_admin", {
id: uuid("id").primaryKey().defaultRandom(),
id_user: uuid("id_user")
.notNull()
.references(() => UserTable.id),
signature: text("signature"),
created_at: timestamp("created_at").notNull().defaultNow(),
});
export const ChartProjectTable = pgTable("chart_project", {
id: uuid("id").primaryKey().defaultRandom(),
id_projek: uuid("id_projek")
.notNull()
.references(() => ProjectTable.id),
nominal: integer("nominal").notNull(),
created_at: timestamp("created_at").notNull().defaultNow(),
updated_at: timestamp("updated_at"),
});
export const ProjectWalletTable = pgTable("project_wallet", {
id: uuid("id").primaryKey().defaultRandom(),
id_projek: uuid("id_projek")
.notNull()
.references(() => ProjectTable.id),
dana_terkumpul: integer("dana_terkumpul").notNull().default(0),
saldo: integer("saldo").notNull().default(0),
created_at: timestamp("created_at").notNull().defaultNow(),
updated_at: timestamp("updated_at"),
});
export const HistoryProjectWalletTable = pgTable("history_project_wallet", {
id: uuid("id").primaryKey().defaultRandom(),
id_project_wallet: uuid("id_project_wallet")
.notNull()
.references(() => ProjectWalletTable.id),
nominal: integer("nominal").notNull(),
dana_tersisa: integer("dana_tersisa").notNull(),
deskripsi: text("deskripsi").notNull(),
bukti_transfer: text("bukti_transfer"),
created_at: timestamp("created_at").notNull().defaultNow(),
});
// Relation definition
export const usersWalletRelations = relations(UserTable, ({ many }) => ({
wallet: many(WalletTable),
project: many(ProjectTable),
}));
export const walletUsersRelations = relations(WalletTable, ({ one }) => ({
author: one(UserTable, {
fields: [WalletTable.id_user],
references: [UserTable.id],
}),
}));
export const walletTopupRelations = relations(WalletTable, ({ many }) => ({
topup: many(TopupTable),
}));
export const topupWalletRelations = relations(TopupTable, ({ one }) => ({
wallet: one(WalletTable, {
fields: [TopupTable.id_wallet],
references: [WalletTable.id],
}),
}));
export const projectCategoryRelations = relations(ProjectCategoryTable, ({ many }) => ({
project: many(ProjectTable),
}));
export const projectRelations = relations(ProjectTable, ({ one, many }) => ({
category: one(ProjectCategoryTable, {
fields: [ProjectTable.id_kategori],
references: [ProjectCategoryTable.id],
}),
document: many(SupportDocumentTable),
report: many(ProjectReportTable),
mutation: many(ProjectMutationReportTable),
history: many(HistoryProjectTable),
wallet: one(ProjectWalletTable),
}));
export const supportDocumentRelations = relations(SupportDocumentTable, ({ one }) => ({
project: one(ProjectTable, {
fields: [SupportDocumentTable.id_projek],
references: [ProjectTable.id],
}),
}));
export const projectReportRelations = relations(ProjectReportTable, ({ one }) => ({
project: one(ProjectTable, {
fields: [ProjectReportTable.id_projek],
references: [ProjectTable.id],
}),
}));
export const projectMutationReportRelations = relations(ProjectMutationReportTable, ({ one }) => ({
project: one(ProjectTable, {
fields: [ProjectMutationReportTable.id_projek],
references: [ProjectTable.id],
}),
}));
export const historyProjectRelations = relations(HistoryProjectTable, ({ one }) => ({
project: one(ProjectTable, {
fields: [HistoryProjectTable.id_projek],
references: [ProjectTable.id],
}),
}));
export const chartProjectRelations = relations(ChartProjectTable, ({ one }) => ({
project: one(ProjectTable, {
fields: [ChartProjectTable.id_projek],
references: [ProjectTable.id],
}),
}));

View File

@ -0,0 +1,142 @@
import { db, connection } from "./db.js";
import {
ProjectCategoryTable,
UserTable,
ProjectTable,
SupportDocumentTable,
TopupTable,
WalletTable,
ProjectMutationReportTable,
ChartProjectTable,
HistoryProjectTable,
ProjectReportTable,
ProjectWalletTable,
HistoryProjectWalletTable,
SignatureAdminTable,
} from "./schema.js";
import { chartProjectData, chartProjectDataFactory } from "./seeder/chart-project.js";
import { historyProjectData, historyProjectDataFactory } from "./seeder/history-project.js";
import { HistoryProjectWalletData } from "./seeder/history-project-wallet.js";
import { projectCategoryData, projectData, projectDataFactory, supportDocumentData, supportDocumentDataFactory } from "./seeder/project.js";
import { projectMutationReportData, projectMutationReportDataFactory } from "./seeder/project-mutation.js";
import { projectReportData, projectReportDataFactory } from "./seeder/project-report.js";
import { ProjectWalletData } from "./seeder/project-wallet.js";
import { topupData, topupDataFactory } from "./seeder/topup.js";
import { userData, userDataFactory } from "./seeder/user.js";
import { walletData, walletDataFactory } from "./seeder/wallet.js";
import { SignatureAdminData } from "./seeder/signature-admin.js";
// Define a helper function for type-safe inserts
async function safeInsert<T extends { [key: string]: any }>(table: T, data: Partial<T["_"]["insert"]>[]) {
if (data.length === 0) return;
try {
await db.insert(table as any).values(data as any);
} catch (error) {
console.error(`Error inserting into ${table.name}:`, error);
console.error("Problematic data:", JSON.stringify(data, null, 2));
throw error;
}
}
async function seed() {
try {
console.log("Menghapus semua data...");
await db.delete(HistoryProjectWalletTable);
await db.delete(ProjectWalletTable);
await db.delete(SupportDocumentTable);
await db.delete(ChartProjectTable);
await db.delete(ProjectReportTable);
await db.delete(ProjectMutationReportTable);
await db.delete(HistoryProjectTable);
await db.delete(ProjectTable);
await db.delete(TopupTable);
await db.delete(WalletTable);
await db.delete(SignatureAdminTable);
await db.delete(UserTable);
await db.delete(ProjectCategoryTable);
console.log("semua data berhasil dihapus 🗑️ 🗑️ 🗑️");
console.log("Memulai seeding...");
const userSeedData = await userData();
// const userSeedFactory = await userDataFactory(10);
await safeInsert(UserTable, userSeedData);
// await safeInsert(UserTable, userSeedFactory);
const userRecords = await db.select().from(UserTable);
const userIds = userRecords.map((row) => row.id);
const projectCategorySeedData = await projectCategoryData();
await safeInsert(ProjectCategoryTable, projectCategorySeedData);
// const projectCategoryRecords = await db.select().from(ProjectCategoryTable);
// const projectCategoryIds = projectCategoryRecords.map((row) => row.id);
// const projectSeedData = await projectData(userIds, projectCategoryIds);
// const projectSeedFactory = await projectDataFactory(userIds, projectCategoryIds, 10);
// await safeInsert(ProjectTable, projectSeedData);
// await safeInsert(ProjectTable, projectSeedFactory);
// const projectRecords = await db.select().from(ProjectTable);
// const projectIds = projectRecords.map((row) => row.id);
// const supportDocumentSeedData = await supportDocumentData(projectIds);
// const supportDocumentSeedFactory = await supportDocumentDataFactory(projectIds, 10);
// await safeInsert(SupportDocumentTable, supportDocumentSeedData);
// await safeInsert(SupportDocumentTable, supportDocumentSeedFactory);
const walletSeedData = await walletData(userIds);
// const walletSeedFactory = await walletDataFactory(userIds, 10, walletSeedData.usedUserIds);
await safeInsert(WalletTable, walletSeedData.data);
// await safeInsert(WalletTable, walletSeedFactory);
const walletRecords = await db.select().from(WalletTable);
const walletIds = walletRecords.map((row) => row.id);
const topupSeedData = await topupData(walletIds);
// const topupSeedFactory = await topupDataFactory(walletIds, 10);
await safeInsert(TopupTable, topupSeedData);
// await safeInsert(TopupTable, topupSeedFactory);
// const projectReportSeedData = await projectReportData(projectIds);
// const projectReportSeedFactory = await projectReportDataFactory(projectIds, 10);
// await safeInsert(ProjectReportTable, projectReportSeedData);
// await safeInsert(ProjectReportTable, projectReportSeedFactory);
// const projectMutationReportSeedData = await projectMutationReportData(projectIds);
// const projectMutationReportSeedFactory = await projectMutationReportDataFactory(projectIds, 10);
// await safeInsert(ProjectMutationReportTable, projectMutationReportSeedData);
// await safeInsert(ProjectMutationReportTable, projectMutationReportSeedFactory);
// const chartProjectSeedData = await chartProjectData(projectIds);
// const chartProjectSeedFactory = await chartProjectDataFactory(projectIds, 10);
// await safeInsert(ChartProjectTable, chartProjectSeedData);
// await safeInsert(ChartProjectTable, chartProjectSeedFactory);
// const historyProjectSeedData = await historyProjectData(projectIds);
// const historyProjectSeedFactory = await historyProjectDataFactory(projectIds, 10);
// await safeInsert(HistoryProjectTable, historyProjectSeedData);
// await safeInsert(HistoryProjectTable, historyProjectSeedFactory);
// const projectWalletSeedData = await ProjectWalletData(projectIds);
// await safeInsert(ProjectWalletTable, projectWalletSeedData.data);
// const projectWalletRecords = await db.select().from(ProjectWalletTable);
// const projectWalletIds = projectWalletRecords.map((row) => row.id);
// const historyProjectWalletSeedData = await HistoryProjectWalletData(projectWalletIds);
// await safeInsert(HistoryProjectWalletTable, historyProjectWalletSeedData.data);
const signatureAdminSeedData = await SignatureAdminData(userIds);
await safeInsert(SignatureAdminTable, signatureAdminSeedData);
console.log("Seeding berhasil 🥳🥳🥳");
} catch (error) {
console.error("Seeding gagal:", error);
} finally {
await connection.end();
}
}
seed();

View File

@ -0,0 +1,52 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const agreementLetterData = async (userIds: string[], projectIds: string[]) => [
{
id: faker.string.uuid(),
id_projek: projectIds[0],
id_user: userIds[0],
nama_proyek: "Proyek peternakan naga",
nama_petugas: "Ali Murrofid",
alamat_petugas: "Ki. Ruecker no 9",
nama_pemilik_proyek: "alimurrofid",
nik: "3501234567890123",
no_hp: "6281234567890",
alamat: "Jl. Raya Tlogomas No. 246",
tanda_tangan: "https://coursius.com/storage/images/thumb/2021_04_08_12_24_32_b46a6b4bb45b6863c78df7e2524b48ae_900x450_thumb.jpg",
nominal_disetujui: 10000000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
id_user: userIds[1],
nama_proyek: "Proyek Jual Beli T-Rex",
nama_petugas: "Ali Murrofid",
alamat_petugas: "Ki. Ruecker no 9",
nama_pemilik_proyek: "budi santoso",
nik: "3501234567890124",
no_hp: "6281234567891",
alamat: "Jl. Darmo No. 10",
tanda_tangan: "https://coursius.com/storage/images/thumb/2021_04_08_12_24_32_b46a6b4bb45b6863c78df7e2524b48ae_900x450_thumb.jpg",
nominal_disetujui: 20000000,
created_at: new Date(),
},
];
export const agreementLetterDataFactory = async (userIds: string[], projectIds: string[], count: number) => {
const promises = Array.from({ length: count }).map(async () => ({
id: faker.string.uuid(),
id_projek: faker.helpers.arrayElement(projectIds),
id_user: faker.helpers.arrayElement(userIds),
nama_proyek: faker.lorem.words(),
nama_petugas: "Ali Murrofid",
alamat_petugas: "Ki. Ruecker no 9",
nama_pemilik_proyek: faker.person.fullName(),
nik: faker.string.numeric({ length: 16 }),
no_hp: `62${faker.string.numeric(11)}`,
alamat: faker.location.streetAddress(),
tanda_tangan: faker.image.avatar(),
nominal_disetujui: parseInt(faker.string.numeric({ length: 8 })),
created_at: new Date(),
}));
return Promise.all(promises);
};

View File

@ -0,0 +1,49 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const chartProjectData = async (projectIds: string[]) => [
{
id: faker.string.uuid(),
id_projek: projectIds[0],
nominal: 200000,
created_at: new Date("2023-12-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_projek: projectIds[0],
nominal: 1500000,
created_at: new Date("2024-03-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_projek: projectIds[0],
nominal: 2300000,
created_at: new Date("2024-06-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
nominal: 400000,
created_at: new Date("2023-12-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
nominal: 1100000,
created_at: new Date("2024-03-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
nominal: -300000,
created_at: new Date("2024-06-10T13:39:24.000Z"),
},
];
export const chartProjectDataFactory = async (projectIds: string[], count: number) => {
const promises = Array.from({ length: count }).map(async () => ({
id: faker.string.uuid(),
id_projek: faker.helpers.arrayElement(projectIds),
nominal: parseInt(faker.string.numeric({ length: 6 })),
created_at: faker.date.past(),
}));
return Promise.all(promises);
};

View File

@ -0,0 +1,55 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const chartTokenData = async (userIds: string[], projectIds: string[]) => [
{
id: faker.string.uuid(),
id_user: userIds[0],
id_projek: projectIds[0],
nominal: 200000,
created_at: new Date("2023-12-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_user: userIds[0],
id_projek: projectIds[0],
nominal: 1500000,
created_at: new Date("2024-03-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_user: userIds[0],
id_projek: projectIds[0],
nominal: 2300000,
created_at: new Date("2024-06-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_user: userIds[0],
id_projek: projectIds[1],
nominal: 400000,
created_at: new Date("2023-12-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_user: userIds[0],
id_projek: projectIds[1],
nominal: 1100000,
created_at: new Date("2024-03-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_user: userIds[0],
id_projek: projectIds[1],
nominal: -300000,
created_at: new Date("2024-06-10T13:39:24.000Z"),
},
];
export const chartTokenDataFactory = async (userIds: string[], projectIds: string[], count: number) => {
const promises = Array.from({ length: count }).map(async () => ({
id: faker.string.uuid(),
id_user: faker.helpers.arrayElement(userIds),
id_projek: faker.helpers.arrayElement(projectIds),
nominal: parseInt(faker.string.numeric({ length: 6 })),
created_at: new Date(),
}));
return Promise.all(promises);
};

View File

@ -0,0 +1,37 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const HistoryProjectWalletData = async (projectIds: string[]) => {
const data = [
{
id: faker.string.uuid(),
id_project_wallet: projectIds[0],
nominal: 100000,
dana_tersisa: 900000,
deskripsi: "Deskripsi",
bukti_transfer: "https://example.com/bukti-transfer.jpg",
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_project_wallet: projectIds[1],
nominal: 200000,
dana_tersisa: 700000,
deskripsi: "Deskripsi",
bukti_transfer: "https://example.com/bukti-transfer.jpg",
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_project_wallet: projectIds[2],
nominal: 300000,
dana_tersisa: 400000,
deskripsi: "Deskripsi",
bukti_transfer: "https://example.com/bukti-transfer.jpg",
created_at: new Date(),
},
];
const usedProjectWalletIds = new Set(data.map((item) => item.id_project_wallet));
return { data, usedProjectWalletIds };
};

View File

@ -0,0 +1,33 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const historyProjectData = async (projectIds: string[]) => [
{
id: faker.string.uuid(),
id_projek: projectIds[0],
history: "Proses Approval dari Komitee Koperasi",
keterangan:
"Project disetujui oleh komitee dengan catatan sebagai berikut: <ul><li><strong>Nominal disetujui:</strong> Rp 10000000</li><li><strong>Jumlah Unit:</strong> 10</li><li><strong>Maks Pembelian:</strong> 10 atau Rp 10000000</li></ul>",
status: "SUCCESS",
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
history: "Proses Approval dari Komitee Koperasi",
keterangan:
"Project disetujui oleh komitee dengan catatan sebagai berikut: <ul><li><strong>Nominal disetujui:</strong> Rp 20000000</li><li><strong>Jumlah Unit:</strong> 20</li><li><strong>Maks Pembelian:</strong> 20 atau Rp 20000000</li></ul>",
status: "SUCCESS",
created_at: new Date(),
},
];
export const historyProjectDataFactory = async (projectIds: string[], count: number) => {
const promises = Array.from({ length: count }).map(async () => ({
id: faker.string.uuid(),
id_projek: faker.helpers.arrayElement(projectIds),
history: faker.lorem.words(),
keterangan: faker.lorem.sentence(),
status: faker.helpers.arrayElement(["SUCCESS", "FAILED", "PENDING"]),
created_at: new Date(),
}));
return Promise.all(promises);
};

View File

@ -0,0 +1,39 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const historyTokenData = async (chartTokenIds: string[]) => [
{
id: faker.string.uuid(),
id_chart_token: chartTokenIds[0],
nilai: 300000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_chart_token: chartTokenIds[0],
nilai: 1600000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_chart_token: chartTokenIds[0],
nilai: 2400000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_chart_token: chartTokenIds[1],
nilai: 600000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_chart_token: chartTokenIds[1],
nilai: 1300000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_chart_token: chartTokenIds[1],
nilai: 1000000,
created_at: new Date(),
},
];

View File

@ -0,0 +1,70 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const projectMutationReportData = async (projectIds: string[]) => [
{
id: faker.string.uuid(),
id_projek: projectIds[0],
judul: "Projek Naga Mutasi 1",
pemasukan: 1000000,
pengeluaran: 800000,
laporan: faker.image.avatar(),
created_at: new Date('2023-12-10T13:39:24.000Z'),
},
{
id: faker.string.uuid(),
id_projek: projectIds[0],
judul: "Projek Naga Mutasi 2",
pemasukan: 2000000,
pengeluaran: 500000,
laporan: faker.image.avatar(),
created_at: new Date('2024-03-10T13:39:24.000Z'),
},
{
id: faker.string.uuid(),
id_projek: projectIds[0],
judul: "Projek Naga Mutasi 3",
pemasukan: 3000000,
pengeluaran: 700000,
laporan: faker.image.avatar(),
created_at: new Date('2024-06-10T13:39:24.000Z'),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
judul: "Projek T-Rex Mutasi 1",
pemasukan: 1200000,
pengeluaran: 800000,
laporan: faker.image.avatar(),
created_at: new Date('2023-12-10T13:39:24.000Z'),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
judul: "Projek T-Rex Mutasi 2",
pemasukan: 2000000,
pengeluaran: 900000,
laporan: faker.image.avatar(),
created_at: new Date('2024-03-10T13:39:24.000Z'),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
judul: "Projek T-Rex Mutasi 3",
pemasukan: 400000,
pengeluaran: 700000,
laporan: faker.image.avatar(),
created_at: new Date('2024-06-10T13:39:24.000Z'),
},
];
export const projectMutationReportDataFactory = async (projectIds: string[], count: number) => {
const promises = Array.from({ length: count }).map(async () => ({
id: faker.string.uuid(),
id_projek: faker.helpers.arrayElement(projectIds),
judul: faker.lorem.words(),
pemasukan: parseInt(faker.string.numeric({ length: 7 })),
pengeluaran: parseInt(faker.string.numeric({ length: 6 })),
laporan: faker.image.avatar(),
created_at: new Date(),
}));
return Promise.all(promises);
};

View File

@ -0,0 +1,71 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const projectReportData = async (projectIds: string[]) => [
{
id: faker.string.uuid(),
id_projek: projectIds[0],
judul: "Laporan Projek Naga 1",
jenis_laporan: "UNTUNG",
nominal: 1000000,
laporan: faker.image.avatar(),
created_at: new Date("2023-12-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_projek: projectIds[0],
judul: "Laporan Projek Naga 2",
jenis_laporan: "RUGI",
nominal: 2000000,
laporan: faker.image.avatar(),
created_at: new Date("2024-03-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_projek: projectIds[0],
judul: "Laporan Projek Naga 3",
jenis_laporan: "UNTUNG",
nominal: 3000000,
laporan: faker.image.avatar(),
created_at: new Date("2024-06-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
judul: "Laporan Projek T-Rex 1",
jenis_laporan: "RUGI",
nominal: 1200000,
laporan: faker.image.avatar(),
created_at: new Date("2023-12-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
judul: "Laporan Projek T-Rex 2",
jenis_laporan: "UNTUNG",
nominal: 2000000,
laporan: faker.image.avatar(),
created_at: new Date("2024-03-10T13:39:24.000Z"),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
judul: "Laporan Projek T-Rex 3",
jenis_laporan: "RUGI",
nominal: 400000,
laporan: faker.image.avatar(),
created_at: new Date("2024-06-10T13:39:24.000Z"),
},
];
export const projectReportDataFactory = async (projectIds: string[], count: number) => {
const promises = Array.from({ length: count }).map(async () => ({
id: faker.string.uuid(),
id_projek: faker.helpers.arrayElement(projectIds),
judul: faker.lorem.sentence(),
jenis_laporan: faker.helpers.arrayElement(["UNTUNG", "RUGI"]),
nominal: parseInt(faker.string.numeric({ length: 7 })),
laporan: faker.image.avatar(),
created_at: faker.date.recent(),
}));
return Promise.all(promises);
};

View File

@ -0,0 +1,31 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const ProjectWalletData = async (projectIds: string[]) => {
const data = [
{
id: faker.string.uuid(),
id_projek: projectIds[0],
dana_terkumpul: 1000000,
saldo: 1000000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1],
dana_terkumpul: 2000000,
saldo: 2000000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_projek: projectIds[2],
dana_terkumpul: 3000000,
saldo: 3000000,
created_at: new Date(),
},
];
const usedProjectIds = new Set(data.map(item => item.id_projek));
return { data, usedProjectIds };
};

View File

@ -0,0 +1,125 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const projectCategoryData = async () => [
{
id: "a2269857-dd6e-43bd-9216-f3f617dde418",
kategori: "Peternakan",
created_at: new Date(),
},
{
id: "b44a051b-e063-4a30-831b-a7b01aaee620",
kategori: "Pertanian",
created_at: new Date(),
},
{
id: "c6480dac-f999-40a0-a468-67974794be17",
kategori: "Jual Beli",
created_at: new Date(),
},
];
export const projectData = async (userIds: string[], categoryIds: string[]) => [
{
id: "5310c3ba-8da7-44f6-9c2d-57363b082aee",
id_user: userIds[0], // Use actual user ID
id_kategori: categoryIds[0], // Use actual category ID
judul: "Proyek Peternakan",
deskripsi: "Proyek peternakan naga",
nominal: 10000000,
asset_jaminan: "Tanah",
nilai_jaminan: 20000000,
lokasi_usaha: "Malang",
detail_lokasi: "Jl. Raya Tlogomas No. 246",
brosur_produk: "https://everpro.id/blog/contoh-brosur-minuman/",
pendapatan_perbulan: 2000000,
pengeluaran_perbulan: 500000,
report_progress: "3",
dokumen_proyeksi: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRm7Ed-GxL_MOmlJj7Zo2pzKtOdS9gKBDHxTA&s",
status: "APPROVAL",
nominal_disetujui: 10000000,
harga_per_unit: 1000000,
jumlah_koin: 10,
minimal_pembelian: 1,
maksimal_pembelian: 10,
limit_siklus: 3,
created_at: new Date("2023-05-10T13:39:24.000Z"),
},
{
id: "848fdf64-a0f8-4dc4-8cc3-81678734edca",
id_user: userIds[0],
id_kategori: categoryIds[2],
judul: "Proyek Jual Beli",
deskripsi: "Proyek Jual Beli T-Rex",
nominal: 20000000,
asset_jaminan: "Tanah",
nilai_jaminan: 40000000,
lokasi_usaha: "Malang",
detail_lokasi: "Jl. Raya Tlogomas No. 246",
brosur_produk: "https://everpro.id/blog/contoh-brosur-minuman/",
pendapatan_perbulan: 2000000,
pengeluaran_perbulan: 500000,
report_progress: "3",
dokumen_proyeksi: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRm7Ed-GxL_MOmlJj7Zo2pzKtOdS9gKBDHxTA&s",
status: "APPROVAL",
nominal_disetujui: 20000000,
harga_per_unit: 1000000,
jumlah_koin: 20,
minimal_pembelian: 1,
maksimal_pembelian: 20,
limit_siklus: 3,
created_at: new Date("2023-05-10T13:39:24.000Z"),
},
];
export const projectDataFactory = async (userIds: string[], categoryIds: string[], count: number) => {
const promises = Array.from({ length: count }).map(async () => ({
id: faker.string.uuid(),
id_user: faker.helpers.arrayElement(userIds), // Use actual user IDs
id_kategori: faker.helpers.arrayElement(categoryIds), // Use actual category IDs
judul: faker.lorem.words(),
deskripsi: faker.lorem.sentence(),
nominal: parseInt(faker.string.numeric({ length: 8 })),
asset_jaminan: faker.lorem.word(),
nilai_jaminan: parseInt(faker.string.numeric({ length: 8 })),
lokasi_usaha: faker.location.city(),
detail_lokasi: faker.location.streetAddress(),
brosur_produk: faker.image.url(),
pendapatan_perbulan: parseInt(faker.string.numeric({ length: 7 })),
pengeluaran_perbulan: parseInt(faker.string.numeric({ length: 6 })),
report_progress: faker.helpers.arrayElement(["1", "3", "6", "12"]),
dokumen_proyeksi: faker.image.url(),
status: faker.helpers.arrayElement(["DRAFT", "PROSES VERIFIKASI", "REVISI", "APPROVAL", "TTD KONTRAK", "PENDANAAN DIBUKA"]),
limit_siklus: 3,
}));
return Promise.all(promises);
};
export const supportDocumentData = async (projectIds: string[]) => [
{
id: faker.string.uuid(),
id_projek: projectIds[0], // Use actual project ID
dokumen: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRm7Ed-GxL_MOmlJj7Zo2pzKtOdS9gKBDHxTA&s",
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_projek: projectIds[1], // Use actual project ID
dokumen: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRm7Ed-GxL_MOmlJj7Zo2pzKtOdS9gKBDHxTA&s",
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_projek: projectIds[2], // Use actual project ID
dokumen: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRm7Ed-GxL_MOmlJj7Zo2pzKtOdS9gKBDHxTA&s",
created_at: new Date(),
},
];
export const supportDocumentDataFactory = async (projectIds: string[], count: number) => {
const promises = Array.from({ length: count }).map(async () => ({
id: faker.string.uuid(),
id_projek: faker.helpers.arrayElement(projectIds), // Use actual project IDs
dokumen: faker.image.url(),
created_at: new Date(),
}));
return Promise.all(promises);
};

View File

@ -0,0 +1,9 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const SignatureAdminData = async (userIds: string[]) => [
{
id: faker.string.uuid(),
id_user: userIds[0],
signature: "/uploads/tanda_tangan_admin/1730169870902-ttd.jpeg",
created_at: new Date(),
},
];

View File

@ -0,0 +1,33 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const tokenData = async (userIds: string[], count: number) => {
const promises = Array.from({ length: count }).flatMap(() => [
{
id: faker.string.uuid(),
id_projek: "5310c3ba-8da7-44f6-9c2d-57363b082aee",
id_user: userIds[1],
nilai: 1000000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_projek: "848fdf64-a0f8-4dc4-8cc3-81678734edca",
id_user: userIds[1],
nilai: 2000000,
created_at: new Date(),
},
]);
return Promise.all(promises);
};
export const tokenDataFactory = async (userIds: string[], projectIds: string[], count: number) => {
const promises = Array.from({ length: count }).map(async () => ({
id: faker.string.uuid(),
id_projek: faker.helpers.arrayElement(projectIds),
id_user: faker.helpers.arrayElement(userIds),
nilai: faker.helpers.arrayElement([1000000, 2000000]),
created_at: new Date(),
}));
return Promise.all(promises);
};

View File

@ -0,0 +1,101 @@
import { faker } from "@faker-js/faker/locale/id_ID";
import { db } from "../db.js"; // Sesuaikan dengan path instance database Anda
import { UserTable, WalletTable } from "../schema.js"; // Sesuaikan dengan path schema yang digunakan
import { eq, inArray } from "drizzle-orm"; // Pastikan `inArray` di-import
// Fungsi untuk mengambil nama pengguna berdasarkan wallet ID
type UserWalletRecord = {
walletId: string;
nama: string | null;
};
const getUserNamesByWallets = async (walletIds: string[]): Promise<Map<string, string>> => {
// Query untuk mengambil data wallet dan nama pengguna terkait
const userWalletRecords = await db
.select({
walletId: WalletTable.id,
nama: UserTable.nama,
})
.from(WalletTable)
.leftJoin(UserTable, eq(UserTable.id, WalletTable.id_user))
.where(inArray(WalletTable.id, walletIds)) as UserWalletRecord[];
// Mapping hasil query ke dalam Map dengan key wallet ID dan value nama pengguna
const walletUserMap = new Map<string, string>();
userWalletRecords.forEach((record) => {
walletUserMap.set(record.walletId, record.nama || 'Unknown User');
});
return walletUserMap;
};
// Fungsi untuk membuat data top-up dengan nama pengguna yang diambil berdasarkan wallet ID
export const topupData = async (walletIds: string[]) => {
const walletUserMap = await getUserNamesByWallets(walletIds);
return [
{
id: faker.string.uuid(),
id_wallet: walletIds[2],
nama: walletUserMap.get(walletIds[2]) || "Unknown User",
nama_bank: "BRI",
no_rekening: "1234567890",
nama_pemilik_rekening: walletUserMap.get(walletIds[2]) || "Unknown User",
nominal: 50000,
jenis: "SIMPANAN POKOK",
status: "SUKSES",
bukti_pembayaran: "https://example.com/bukti-pembayaran.jpg",
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_wallet: walletIds[3],
nama: walletUserMap.get(walletIds[3]) || "Unknown User",
nama_bank: "BCA",
no_rekening: "0987654321",
nama_pemilik_rekening: walletUserMap.get(walletIds[3]) || "Unknown User",
nominal: 120000,
jenis: "SIMPANAN WAJIB",
status: "SUKSES",
bukti_pembayaran: "https://example.com/bukti-pembayaran.jpg",
created_at: new Date(),
},
];
};
// Fungsi factory untuk menghasilkan beberapa data top-up dengan nama pengguna berdasarkan wallet ID
export const topupDataFactory = async (walletIds: string[], count: number) => {
const walletUserMap = await getUserNamesByWallets(walletIds);
const promises = Array.from({ length: count }).map(async () => {
const selectedWalletId = faker.helpers.arrayElement(walletIds);
const jenis = faker.helpers.arrayElement(["SIMPANAN WAJIB", "SIMPANAN POKOK", "TOPUP SALDO"]);
// Menentukan nominal berdasarkan jenis simpanan
let nominal;
if (jenis === "SIMPANAN WAJIB") {
nominal = 50000;
} else if (jenis === "SIMPANAN POKOK") {
nominal = 120000;
} else {
// Jika jenis adalah "TOPUP SALDO", gunakan nominal acak
nominal = parseInt(faker.string.numeric({ length: 6 }));
}
return {
id: faker.string.uuid(),
id_wallet: selectedWalletId,
nama: walletUserMap.get(selectedWalletId) || "Unknown User",
nama_bank: "MANDIRI",
no_rekening: faker.finance.accountNumber(),
nama_pemilik_rekening: walletUserMap.get(selectedWalletId) || "Unknown User",
nominal,
jenis,
status: faker.helpers.arrayElement(["SUKSES", "GAGAL", "MENUNGGU KONFIRMASI"]),
bukti_pembayaran: faker.image.avatar(),
created_at: new Date(),
};
});
return Promise.all(promises);
};

View File

@ -0,0 +1,37 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const TransactionData = async (projectIds: string[], userIds: string[]) => [
{
id: "6b0005e5-0ae4-4691-b019-e88b7241b34b",
id_user: userIds[1],
nama_user: "John Doe",
id_projek: projectIds[2],
judul_projek: "Projek Naga",
owner_projek: "Budiono Siregar",
jumlah_token: 10,
total_nominal: 1000000,
created_at: new Date(),
},
{
id: "77b41ce9-fb75-4e05-9276-e97e0ebee6d1",
id_user: userIds[1],
nama_user: "Rudi Doe",
id_projek: projectIds[2],
judul_projek: "Projek T-Rex",
owner_projek: "Budi Siregar",
jumlah_token: 20,
total_nominal: 2000000,
created_at: new Date(),
},
{
id: "8f9de48b-cfdb-4f33-8494-64418b0935de",
id_user: userIds[1],
nama_user: "John Doe",
id_projek: projectIds[2],
judul_projek: "Projek Naga",
owner_projek: "Budiono Siregar",
jumlah_token: 30,
total_nominal: 3000000,
created_at: new Date(),
}
];

View File

@ -0,0 +1,91 @@
import { faker } from "@faker-js/faker/locale/id_ID";
import { hash } from "bcryptjs";
import { format, subMonths } from "date-fns";
export const userData = async () => [
{
id: "0937d7fc-8aa2-4ce5-b437-64b755ce5ff6",
nama: "Admin Koperasi",
no_hp: "6281234567890",
role: "ADMIN",
status: "AKTIF",
password: await hash("password", 10),
tempat_lahir: "Malang",
tanggal_lahir: format(new Date("2002-02-02"), "yyyy-MM-dd"),
provinsi: "35",
kota: "35.73",
kecamatan: "35.73.05",
alamat: "Jl. Raya Tlogomas No. 246",
nik: "3501234567890123",
foto_diri: "/uploads/foto_diri/1730168622601-fotodiri.png",
foto_ktp: "/uploads/foto_ktp/1730168622601-fotoktp.png",
created_at: new Date(),
},
{
id: "1d798a08-0602-4030-b8df-b808134ec171",
nama: "budiono siregar",
no_hp: "6281234567891",
role: "BASIC",
status: "AKTIF",
password: await hash("password", 10),
tempat_lahir: "Surabaya",
tanggal_lahir: format(new Date("2003-03-03"), "yyyy-MM-dd"),
provinsi: "35",
kota: "35.78",
kecamatan: "35.78.03",
alamat: "Jl. Darmo No. 10",
nik: "3501234567890124",
foto_diri: "/uploads/foto_diri/1730168622602-fotodiri.png",
foto_ktp: "/uploads/foto_ktp/1730168622602-fotoktp.png",
created_at: new Date(),
otp: "2233",
},
{
id: "a258fc20-32fd-4bce-9a99-85dd2551fab7",
nama: "siti nurhaliza",
no_hp: "6281234567892",
role: "PLATINUM",
status: "AKTIF",
password: await hash("password", 10),
tempat_lahir: "Jakarta",
tanggal_lahir: format(new Date("2004-04-04"), "yyyy-MM-dd"),
provinsi: "31",
kota: "31.74",
kecamatan: "31.74.01",
alamat: "Jl. Mampang Prapatan No. 20",
nik: "3501234567890125",
foto_diri: "/uploads/foto_diri/1730168622603-fotodiri.png",
foto_ktp: "/uploads/foto_ktp/1730168622603-fotoktp.png",
created_at: new Date(),
otp: "2234",
},
];
export const userDataFactory = async (count: any) => {
const promises = Array.from({ length: count }).map(async () => {
const birthDate = subMonths(new Date(), faker.number.int({ min: 1, max: 12 }));
return {
id: faker.string.uuid(),
nama: faker.person.fullName(),
no_hp: `62${faker.string.numeric(11)}`,
role: "BASIC",
status: "TIDAK AKTIF",
password: await hash("password", 10),
tempat_lahir: faker.location.city(),
tanggal_lahir: format(birthDate, "yyyy-MM-dd"),
provinsi: "35",
kota: "35.73",
kecamatan: faker.helpers.arrayElement(["35.73.01", "35.73.02", "35.73.03", "35.73.04", "35.73.05"]),
alamat: faker.location.streetAddress(),
nik: faker.string.numeric({ length: 16 }),
foto_diri: faker.image.avatar(),
foto_ktp: faker.image.avatar(),
created_at: new Date(),
verification_token: faker.string.numeric({ length: 64 }),
is_verified: true,
};
});
return Promise.all(promises);
};

View File

@ -0,0 +1,89 @@
import { faker } from "@faker-js/faker/locale/id_ID";
export const walletData = async (userIds: string[]) => {
const data = [
{
id: faker.string.uuid(),
id_user: userIds[0],
jenis_wallet: "SIMPANAN POKOK",
saldo: 50_000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_user: userIds[0],
jenis_wallet: "SIMPANAN WAJIB",
saldo: 120_000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_user: userIds[1],
jenis_wallet: "SIMPANAN POKOK",
saldo: 50_000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_user: userIds[1],
jenis_wallet: "SIMPANAN WAJIB",
saldo: 120_000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_user: userIds[2],
jenis_wallet: "SIMPANAN POKOK",
saldo: 50_000,
created_at: new Date(),
},
{
id: faker.string.uuid(),
id_user: userIds[2],
jenis_wallet: "SIMPANAN WAJIB",
saldo: 120_000,
created_at: new Date(),
},
];
const usedUserIds = new Set(data.map((item) => item.id_user));
return { data, usedUserIds };
};
export const walletDataFactory = async (userIds: string[], count: number, usedUserIds: Set<string>) => {
const availableUserIds = userIds.filter((id) => !usedUserIds.has(id));
const usedIds = new Set<string>();
const promises = Array.from({ length: count }).map(async () => {
let selectedUserId: string;
do {
selectedUserId = faker.helpers.arrayElement(availableUserIds);
} while (usedIds.has(selectedUserId));
usedIds.add(selectedUserId);
const jenis_wallet = faker.helpers.arrayElement(["SIMPANAN WAJIB", "SIMPANAN POKOK", "SALDO"]);
let nominal;
if (jenis_wallet === "SIMPANAN WAJIB") {
nominal = 50000;
} else if (jenis_wallet === "SIMPANAN POKOK") {
nominal = 120000;
} else {
nominal = parseInt(faker.string.numeric({ length: 6 }));
}
return {
id: faker.string.uuid(),
id_user: selectedUserId,
jenis_wallet,
saldo: parseInt(faker.string.numeric({ length: 6 })),
created_at: new Date(),
};
});
return Promise.all(promises);
};

View File

@ -0,0 +1,119 @@
import express, { Request, Response, NextFunction } from "express";
import cors from "cors";
import authRouter from "./routes/auth.js";
import userRouter from "./routes/user.js";
import projectCategoryRouter from "./routes/project-category.js";
import projectRouter from "./routes/project.js";
import wilayahRouter from "./routes/wilayah.js";
import projectReportRouter from "./routes/project-report.js";
import topupRouter from "./routes/topup.js";
import projectTokenRouter from "./routes/project-token.js";
import mutationRouter from "./routes/mutation.js";
import chartProjectRouter from "./routes/chart-project.js";
import chartTokenRouter from "./routes/chart-token.js";
import historyTokenRouter from "./routes/history-token.js";
import historyProjectRouter from "./routes/history-project.js";
import whatsappRouter from "./routes/baileys.js";
import projectWalletRouter from "./routes/project-wallet.js";
import historyProjectWalletRouter from "./routes/history-project-wallet.js";
import transactionRouter from "./routes/transaction.js";
import swaggerUi from "swagger-ui-express";
import { ethers } from "ethers";
import path from "path";
import fs from "fs";
import { configureAxiosInterceptor } from "./middlewares/api-key.js";
if (!process.env.API_URL) {
throw new Error("Api Url is not defined in the environment variables.");
}
if (!process.env.PRIVATE_KEY) {
throw new Error("Private key is not defined in the environment variables.");
}
if (!process.env.CONTRACT_ADDRESS) {
throw new Error("Contract address is not defined in the environment variables.");
}
export const walletServiceUrl: string = process.env.WALLET_URL || "http://localhost:3001";
export const walletServiceApiKey: any = process.env.API_KEY;
const apiUrl: string = process.env.API_URL;
const privateKey: string = process.env.PRIVATE_KEY;
const contractAddress: string = process.env.CONTRACT_ADDRESS;
const abiPath = path.join(__dirname, "../contractABI.json");
const contractABI = JSON.parse(fs.readFileSync(abiPath, "utf8"));
const provider = new ethers.JsonRpcProvider(apiUrl);
async function setupContract() {
try {
// Create a wallet instance from the private key
const signer = new ethers.Wallet(privateKey, provider);
console.log("\x1b[36m%s\x1b[0m", "Signer 👲 :", await signer.getAddress());
// Create the contract instance with the signer
const contract = new ethers.Contract(contractAddress, contractABI, signer);
console.log("\x1b[36m%s\x1b[0m", "Contract instance 🚀");
// Check if the contract instance is valid
if (!contract || typeof contract !== "object") {
throw new Error("Contract instance is not valid");
}
return contract;
} catch (error) {
console.error("Error in setupContract:", error);
throw error;
}
}
export const getContract = setupContract;
const app = express();
const port = process.env.PORT || 3000;
configureAxiosInterceptor(walletServiceUrl, walletServiceApiKey);
app.use(express.json());
app.use(cors());
app.use(express.static(__dirname + "/../assets"));
app.get("/formatan-dokumen-proyeksi", (req: Request, res: Response) => {
const file = `statis/formatan-dokumen-proyeksi.docx`;
res.json({ data: file });
});
app.get("/", (req: Request, res: Response) => {
res.send("Hello, Koperasi Service!");
});
app.use("/auth", authRouter);
app.use("/user", userRouter);
app.use("/project", projectRouter);
app.use("/project-category", projectCategoryRouter);
app.use("/project-report", projectReportRouter);
app.use("/wilayah", wilayahRouter);
app.use("/topup", topupRouter);
app.use("/token", projectTokenRouter);
app.use("/mutation", mutationRouter);
app.use("/chart-project", chartProjectRouter);
app.use("/chart-token", chartTokenRouter);
app.use("/history-token", historyTokenRouter);
app.use("/whatsapp", whatsappRouter);
app.use("/project-wallet", projectWalletRouter);
app.use("/history-project-wallet", historyProjectWalletRouter);
app.use("/history-project", historyProjectRouter);
app.use("/transaction", transactionRouter);
const swaggerDocument = require("../../../api-docs/swagger-output.json");
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument));
// Add this error handling middleware
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err.stack);
res.status(500).send("Something went wrong");
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});

View File

@ -0,0 +1,40 @@
// middlewares/agreement-letter.ts
import { NextFunction, Request, Response } from "express";
import multer from "multer";
import path from "path";
// Set up Multer storage
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, path.join(__dirname, "../../assets/uploads/tanda_tangan"));
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
},
});
// Initialize Multer with storage configuration
const upload = multer({ storage });
// Middleware to handle file upload and process the uploaded file
export const AgreementMiddleware = (req: Request, res: Response, next: NextFunction) => {
upload.single("tanda_tangan")(req, res, (err) => {
if (err) {
console.error("Error in file upload:", err);
return res.status(500).json({ message: "File upload failed" });
}
const file = req.file;
// Check if file upload was successful
if (!file) {
return res.status(400).json({ message: "File upload failed" });
}
// Set the file path to the request body
req.body.tanda_tangan = `/uploads/tanda_tangan/${file.filename}`;
next();
});
};

View File

@ -0,0 +1,15 @@
import axios from "axios";
export const configureAxiosInterceptor = (walletServiceUrl: string, walletServiceApiKey: any) => {
axios.interceptors.request.use(
(config) => {
if (config.baseURL === walletServiceUrl || (config.url && config.url.startsWith(walletServiceUrl))) {
config.headers["x-api-key"] = walletServiceApiKey;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
};

View File

@ -0,0 +1,110 @@
import multer from "multer";
import path from "path";
import { NextFunction, Request, Response } from "express";
import jwt from "jsonwebtoken";
import { tokenBlacklist } from "../services/jwt.js";
import { db } from "../drizzle/db.js";
import { UserTable } from "../drizzle/schema.js";
import { eq } from "drizzle-orm";
// Configure disk storage for multer
const storage = multer.diskStorage({
destination: (req, file, cb) => {
switch (file.fieldname) {
case "foto_diri":
cb(null, path.join(__dirname, "../../assets/uploads/foto_diri"));
break;
case "foto_ktp":
cb(null, path.join(__dirname, "../../assets/uploads/foto_ktp"));
break;
case "foto_profile":
cb(null, path.join(__dirname, "../../assets/uploads/foto_profile"));
break;
case "signature":
cb(null, path.join(__dirname, "../../assets/uploads/tanda_tangan_admin"));
break;
default:
cb(new Error("Invalid field name"), "");
}
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
},
});
const upload = multer({ storage });
export const parseFormData = upload.fields([
{ name: "foto_diri", maxCount: 1 },
{ name: "foto_ktp", maxCount: 1 },
{ name: "foto_profile", maxCount: 1 },
{ name: "signature", maxCount: 1 },
]);
export const formDataParserMiddleware = (req: Request, res: Response, next: NextFunction) => {
try {
const files = req.files as { [fieldname: string]: Express.Multer.File[] } | undefined;
// Initialize paths as null or uploaded file values
const fotoDiriPath = files?.["foto_diri"]?.[0] ? `/uploads/foto_diri/${files["foto_diri"][0].filename}` : null;
const fotoKTPPath = files?.["foto_ktp"]?.[0] ? `/uploads/foto_ktp/${files["foto_ktp"][0].filename}` : null;
const fotoProfilePath = files?.["foto_profile"]?.[0] ? `/uploads/foto_profile/${files["foto_profile"][0].filename}` : null;
const signaturePath = files?.["signature"]?.[0] ? `/uploads/tanda_tangan_admin/${files["signature"][0].filename}` : null;
// Set paths in req.body
if (fotoDiriPath) req.body.foto_diri = fotoDiriPath;
if (fotoKTPPath) req.body.foto_ktp = fotoKTPPath;
if (fotoProfilePath) req.body.foto_profile = fotoProfilePath;
if (signaturePath) req.body.signature = signaturePath;
next();
} catch (err) {
console.error("Error parsing form-data: ", err);
res.status(500).json({ message: "Error parsing form-data" });
}
};
export const validateToken = async (req: Request, res: Response, next: NextFunction) => {
try {
const token = req.headers.authorization?.split(" ")[1];
if (!token) {
return res.status(401).json({ message: "JWT Token not found" });
}
if (tokenBlacklist[token]) {
return res.status(401).json({ error: "JWT Token has been invalidated" });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET as string);
req.user = decoded;
next();
} catch (err) {
res.status(401).json({ message: "Invalid token" });
}
};
export const adminOnly = async (req: Request, res: Response, next: NextFunction) => {
try {
const user = await db.select().from(UserTable).where(eq(UserTable.id, req.user.id)).limit(1).execute();
if (!user[0] || user[0].role !== "ADMIN") {
return res.status(403).json({ message: "Forbidden" });
}
next();
} catch (err) {
res.status(500).json({ message: "Internal server error" });
}
};
export const platinumOnly = async (req: Request, res: Response, next: NextFunction) => {
try {
if (req.user.role !== "PLATINUM" && req.user.role !== "ADMIN") {
return res.status(403).json({ message: "Forbidden" });
}
next();
} catch (err) {
res.status(500).json({ message: "Internal server error" });
}
};

View File

@ -0,0 +1,34 @@
import { NextFunction, Request, Response } from "express";
import multer from "multer";
import path from "path";
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, path.join(__dirname, "../../assets/uploads/bukti_transfer"));
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
},
});
const upload = multer({ storage });
export const uploadBuktiTransferMiddleware = (req: Request, res: Response, next: NextFunction) => {
console.log(req.user);
upload.single("bukti_transfer")(req, res, (err) => {
if (err) {
console.error("Error in file upload:", err);
return res.status(500).json({ message: "File upload failed" });
}
const file = req.file;
if (!file) {
return res.status(400).json({ message: "File upload failed" });
}
req.body.bukti_transfer = `/uploads/bukti_transfer/${file.filename}`;
next();
});
};

View File

@ -0,0 +1,33 @@
import { NextFunction, Request, Response } from "express";
import multer from "multer";
import path from "path";
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, path.join(__dirname, "../../assets/uploads/laporan_mutasi"));
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
},
});
const upload = multer({ storage });
export const uploadMutationMiddleware = (req: Request, res: Response, next: NextFunction) => {
upload.single("laporan")(req, res, (err) => {
if (err) {
console.error("Error in file upload:", err);
return res.status(500).json({ message: "File upload failed" });
}
const file = req.file;
if (!file) {
return res.status(400).json({ message: "File upload failed" });
}
req.body.laporan = `/uploads/laporan_mutasi/${file.filename}`;
next();
});
};

View File

@ -0,0 +1,33 @@
import { NextFunction, Request, Response } from "express";
import multer from "multer";
import path from "path";
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, path.join(__dirname, "../../assets/uploads/laporan_keuangan"));
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
},
});
const upload = multer({ storage });
export const uploadLaporanMiddleware = (req: Request, res: Response, next: NextFunction) => {
upload.single("laporan")(req, res, (err) => {
if (err) {
console.error("Error in file upload:", err);
return res.status(500).json({ message: "File upload failed" });
}
const file = req.file;
if (!file) {
return res.status(400).json({ message: "File upload failed" });
}
req.body.laporan = `/uploads/laporan_keuangan/${file.filename}`;
next();
});
};

View File

@ -0,0 +1,150 @@
import multer from "multer";
import path from "path";
import { Request, Response, NextFunction } from "express";
// Definisikan tipe untuk file yang diizinkan
type AllowedFileTypes = {
[key: string]: string[];
};
// Tipe file yang diizinkan
const allowedFileTypes: AllowedFileTypes = {
brosur_produk: ["image/jpg", "image/jpeg", "image/png", "application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
dokumen_proyeksi: ["image/jpg", "image/jpeg", "image/png", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "text/csv", "application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
dokumen: ["image/jpg", "image/jpeg", "image/png", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "text/csv", "application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
dokumen_prospektus: ["image/jpg", "image/jpeg", "image/png", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "text/csv", "application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
};
// Maksimum ukuran file dalam bytes (2MB)
const maxSize = 2 * 1024 * 1024;
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const uploadPath = path.join(__dirname, "../../assets/uploads");
const folderName = file.fieldname === "dokumen" ? "dokumen_pendukung" : file.fieldname;
cb(null, path.join(uploadPath, folderName));
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
},
});
// Validasi tipe dan ukuran file
const fileFilter = (req: Request, file: Express.Multer.File, cb: multer.FileFilterCallback) => {
const fileTypes = allowedFileTypes[file.fieldname];
if (fileTypes && fileTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error(`File type not allowed for ${file.fieldname}.`));
}
};
const upload = multer({
storage,
fileFilter,
limits: { fileSize: maxSize },
}).fields([
{ name: "brosur_produk", maxCount: 1 },
{ name: "dokumen_proyeksi", maxCount: 1 },
{ name: "dokumen", maxCount: 5 },
{ name: "dokumen_prospektus", maxCount: 1 },
]);
// Helper function to process uploaded files
const processUploadedFiles = (req: Request, files: Express.Multer.File[] | undefined, fieldName: string): string | string[] | null => {
if (!files || files.length === 0) return null;
const basePath = `uploads/${fieldName === "dokumen" ? "dokumen_pendukung" : fieldName}`;
return fieldName === "dokumen"
? files.map(file => `${basePath}/${file.filename}`)
: `${basePath}/${files[0].filename}`;
};
export const uploadMiddleware = (req: Request, res: Response, next: NextFunction) => {
upload(req, res, (err) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({ error: `Multer error: ${err.message}` });
} else if (err) {
return res.status(400).json({ error: err.message });
}
const files = req.files as { [fieldname: string]: Express.Multer.File[] } | undefined;
if (!files) {
return res.status(400).json({ error: "No files were uploaded." });
}
console.log("Files yang tertangkap:", files);
const fieldNames = ["brosur_produk", "dokumen_proyeksi", "dokumen", "dokumen_prospektus"];
fieldNames.forEach(fieldName => {
const processedFiles = processUploadedFiles(req, files[fieldName], fieldName);
if (processedFiles) {
req.body[fieldName] = processedFiles;
}
});
if (!req.body.dokumen_proyeksi) {
return res.status(400).json({ error: "File dokumen_proyeksi is required." });
}
next();
});
};
export const uploadUpdateProjectMiddleware = (req: Request, res: Response, next: NextFunction) => {
upload(req, res, (err) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({ error: `Multer error: ${err.message}` });
} else if (err) {
return res.status(400).json({ error: err.message });
}
const files = req.files as { [fieldname: string]: Express.Multer.File[] } | undefined;
if (!files) {
return res.status(400).json({ error: "No files were uploaded." });
}
console.log("Files yang tertangkap:", files);
const fieldNames = ["brosur_produk", "dokumen_proyeksi", "dokumen"];
fieldNames.forEach(fieldName => {
const processedFiles = processUploadedFiles(req, files[fieldName], fieldName);
if (processedFiles) {
req.body[fieldName] = processedFiles;
}
});
next();
});
};
export const uploadDokumenProspektusMiddleware = (req: Request, res: Response, next: NextFunction) => {
upload(req, res, (err) => {
if (err instanceof multer.MulterError) {
return res.status(400).json({ error: `Multer error: ${err.message}` });
} else if (err) {
return res.status(400).json({ error: err.message });
}
const files = req.files as { [fieldname: string]: Express.Multer.File[] } | undefined;
if (!files) {
return res.status(400).json({ error: "No files were uploaded." });
}
const processedFiles = processUploadedFiles(req, files.dokumen_prospektus, "dokumen_prospektus");
if (processedFiles) {
req.body.dokumen_prospektus = processedFiles;
} else {
return res.status(400).json({ error: "File dokumen_prospektus is required." });
}
next();
});
};

View File

@ -0,0 +1,34 @@
import { NextFunction, Request, Response } from "express";
import multer from "multer";
import path from "path";
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, path.join(__dirname, "../../assets/uploads/bukti_pembayaran"));
},
filename: (req, file, cb) => {
cb(null, `${Date.now()}-${file.originalname}`);
},
});
const upload = multer({ storage });
export const uploadBuktiMiddleware = (req: Request, res: Response, next: NextFunction) => {
console.log(req.user);
upload.single("bukti_pembayaran")(req, res, (err) => {
if (err) {
console.error("Error in file upload:", err);
return res.status(500).json({ message: "File upload failed" });
}
const file = req.file;
if (!file) {
return res.status(400).json({ message: "File upload failed" });
}
req.body.bukti_pembayaran = `/uploads/bukti_pembayaran/${file.filename}`;
next();
});
};

View File

@ -0,0 +1,24 @@
import type { Request, Response, NextFunction } from "express";
import type { AnyZodObject } from "zod";
import { fromZodError } from "zod-validation-error";
// import { UnprocessableEntityException } from "../exceptions/validation";
// import { ErrorCode } from "../exceptions/root";
export const validateResource =
(schema: AnyZodObject) =>
(req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse({
body: req.body,
query: req.query,
params: req.params,
});
if (!result.success) {
return res.status(400).json({ error: fromZodError(result.error) });
// throw new UnprocessableEntityException("Validation error", ErrorCode.UNPROCESSABLE_ENTITY, fromZodError(result.error))
}
next();
};
export default validateResource;

View File

@ -0,0 +1,13 @@
import express from "express";
import * as authController from "../controllers/auth.js";
import validateResource from "../middlewares/validate.resource.js";
import { RegisterValidation } from "../validations/auth.js";
import { parseFormData, formDataParserMiddleware, validateToken } from "../middlewares/auth.js";
const router = express.Router();
router.post("/register", parseFormData, formDataParserMiddleware, validateResource(RegisterValidation), authController.register);
router.post("/login", authController.login);
router.post("/logout", validateToken, authController.logout);
export default router;

View File

@ -0,0 +1,11 @@
// routes/whatsapp.ts
import express from "express";
import { getQRCodeHandler } from "../controllers/baileys.js";
import { validateToken, adminOnly } from '../middlewares/auth.js';
const router = express.Router();
// Endpoint to get the QR code
router.get("/qr-code", validateToken, adminOnly,getQRCodeHandler);
export default router;

View File

@ -0,0 +1,9 @@
import express from "express";
import { getAllChartProjectByUserIdHandler, getChartProjectByIdHandler } from "../controllers/chart-project.js";
import { validateToken } from '../middlewares/auth.js';
const router = express.Router();
router.get("/user", validateToken,getAllChartProjectByUserIdHandler);
router.get("/:id", validateToken, getChartProjectByIdHandler);
export default router;

View File

@ -0,0 +1,10 @@
import express from "express";
import { validateToken } from '../middlewares/auth.js';
import { getAllChartTokenByUserIdHandler, getChartTokenByUserIdandProjectIdHandler, getLastChartTokenByUserIdandProjectIdHandler } from "../controllers/chart-token.js";
const router = express.Router();
router.get("/user/project/:id/latest", validateToken, getLastChartTokenByUserIdandProjectIdHandler);
router.get("/user/project/:id", validateToken, getChartTokenByUserIdandProjectIdHandler);
router.get("/user", validateToken, getAllChartTokenByUserIdHandler);
export default router;

View File

@ -0,0 +1,8 @@
import express from "express";
import { validateToken } from '../middlewares/auth.js';
import { getHistoryProjectWalletByProjectWalletIdHandler } from "../controllers/history-project-wallet.js";
const router = express.Router();
router.get("/project-wallet/:id", validateToken, getHistoryProjectWalletByProjectWalletIdHandler);
export default router;

View File

@ -0,0 +1,8 @@
import express from "express";
import { validateToken } from '../middlewares/auth.js';
import { getHistoryProjectByProjectIdHandler } from "../controllers/history-project.js";
const router = express.Router();
router.get("/project/:id", validateToken, getHistoryProjectByProjectIdHandler);
export default router;

View File

@ -0,0 +1,8 @@
import express from "express";
import { validateToken } from '../middlewares/auth.js';
import { getHistoryTokenByUserIdandProjectIdHandler } from "../controllers/history-token.js";
const router = express.Router();
router.get("/project/:id", validateToken, getHistoryTokenByUserIdandProjectIdHandler);
export default router;

View File

@ -0,0 +1,14 @@
import express from "express";
import { uploadMutationMiddleware } from "../middlewares/mutation.js";
import { createMutationHandler, getAllMutationHandler, getMutationByIdHandler, getMutationByProjectIdHandler } from "../controllers/mutation.js";
import { validateToken, adminOnly } from "../middlewares/auth.js";
import validateResource from "../middlewares/validate.resource.js";
import { CreateMutationValidation } from "../validations/mutation.js";
const router = express.Router();
router.post("/", validateToken, adminOnly, uploadMutationMiddleware, validateResource(CreateMutationValidation), createMutationHandler);
router.get("/", validateToken, getAllMutationHandler);
router.get("/:id", validateToken, getMutationByIdHandler);
router.get("/project/:id", validateToken, getMutationByProjectIdHandler);
export default router;

View File

@ -0,0 +1,15 @@
import express from "express";
import * as projectCategoryController from "../controllers/project-category.js";
import { adminOnly, validateToken } from "../middlewares/auth.js";
import validateResource from "../middlewares/validate.resource.js";
import { CreateProjectCategoryValidation, UpdateProjectCategoryValidation } from "../validations/project-category.js";
const router = express.Router();
router.post("/", validateToken, adminOnly, validateResource(CreateProjectCategoryValidation), projectCategoryController.createProjectCategoryHandler);
router.get("/", validateToken, projectCategoryController.getProjectCategoryHandler);
router.get("/:id", validateToken, projectCategoryController.getProjectCategoryByIdHandler);
router.put("/", validateToken, adminOnly, validateResource(UpdateProjectCategoryValidation), projectCategoryController.updateProjectCategoryByIdHandler);
router.delete("/", validateToken, adminOnly, projectCategoryController.deleteProjectCategoryByIdHandler);
export default router;

View File

@ -0,0 +1,15 @@
import express from "express";
import * as projectReportController from "../controllers/project-report.js";
import { adminOnly, validateToken } from "../middlewares/auth.js";
import { uploadLaporanMiddleware } from "../middlewares/project-report.js";
import validateResource from "../middlewares/validate.resource.js";
import { CreateProjectReportValidation } from "../validations/project-report.js";
const router = express.Router();
router.post("/", validateToken, adminOnly, validateResource(CreateProjectReportValidation), projectReportController.createProjectReportHandler);
router.get("/:id", validateToken, projectReportController.getProjectReportHandler);
router.get("/project/:id", validateToken, projectReportController.getProjectReportByProjectIdHandler);
router.get("/", validateToken, projectReportController.getAllProjectReportHandler);
export default router;

View File

@ -0,0 +1,18 @@
import express from "express";
import * as projectTokenController from "../controllers/project-token.js";
import { adminOnly, validateToken } from "../middlewares/auth.js";
import validateResource from "../middlewares/validate.resource.js";
import { BuyTokenValidation } from "../validations/project-token.js";
const router = express.Router();
router.get("/", validateToken, adminOnly, projectTokenController.getAllTokenHandler);
router.get("/total-token", validateToken, projectTokenController.getTotalTokenHandler);
router.get("/project/:id", validateToken, projectTokenController.getTokenByIdProjectHandler);
router.get("/total-usage/:id", validateToken, projectTokenController.getTotalTokenUsageByIdProjectHandler);
router.get("/project/:id/user", validateToken, projectTokenController.getTokenProjectByUserHandler);
router.post("/buy-token", validateToken, validateResource(BuyTokenValidation), projectTokenController.buyTokenProjectHandler);
router.get("/usage-details", validateToken, projectTokenController.tokenUsageDetailsByIdUserHandler);
router.get("/total-token-rupiah", validateToken, projectTokenController.getTotalTokenRupiahByUserHandler);
export default router;

View File

@ -0,0 +1,14 @@
import express from "express";
import * as projectWalletController from "../controllers/project-wallet.js";
import { adminOnly, validateToken } from "../middlewares/auth.js";
import { uploadBuktiTransferMiddleware } from "../middlewares/history-project-wallet.js";
import validateResource from "../middlewares/validate.resource.js";
import { TransferSaldoProjectWalletValidation } from "../validations/project-wallet.js";
const router = express.Router();
router.get("/", validateToken, adminOnly, projectWalletController.getProjectWalletHandler);
router.get("/project/:id", validateToken, adminOnly, projectWalletController.getProjectWalletByProjectIdHandler);
router.post("/transfer-saldo", validateToken, adminOnly, uploadBuktiTransferMiddleware, validateResource(TransferSaldoProjectWalletValidation), projectWalletController.transferSaldoProjectHandler);
export default router;

View File

@ -0,0 +1,53 @@
import express from "express";
import {
acceptProjectHandler,
approveProjectHandler,
checkProjectFundingOpenedHandler,
completingProjectHandler,
countProjectHandler,
createProjectHandler,
getAllProjectHandler,
getDokumenProspektusByIdHandler,
getKeteranganReviseProjectByIdHandler,
getProjectByIdHandler,
getProjectByUserIdHandler,
getUserHaveTokenInProjectHandler,
publishProjectHandler,
rejectProjectHandler,
reviseProjectHandler,
shareProfitHandler,
totalProfitHandler,
updateProjectHandler,
} from "../controllers/project.js";
import validateResource from "../middlewares/validate.resource.js";
import { CreateProjectValidation, UpdateProjectValidation } from "../validations/project.js";
import { uploadDokumenProspektusMiddleware, uploadMiddleware, uploadUpdateProjectMiddleware } from "../middlewares/project.js";
import { adminOnly, platinumOnly, validateToken } from "../middlewares/auth.js";
import { AgreementMiddleware } from "../middlewares/agreement-letter.js";
import { createAgreementLetterHandler, getAgreementLetterByProjectIdHandler, getAllAgreementLetterHandler } from "../controllers/agreement-letter.js";
const router = express.Router();
router.post("/", validateToken, uploadMiddleware, validateResource(CreateProjectValidation), createProjectHandler);
router.get("/count", validateToken, adminOnly, countProjectHandler);
router.get("/", validateToken, platinumOnly, getAllProjectHandler);
router.get("/user", validateToken, getProjectByUserIdHandler);
router.get("/check-funding", validateToken, adminOnly, checkProjectFundingOpenedHandler);
router.put("/update", validateToken, uploadUpdateProjectMiddleware, validateResource(UpdateProjectValidation), updateProjectHandler);
router.post("/agreement-letter", validateToken, AgreementMiddleware, createAgreementLetterHandler);
router.get("/agreement-letter", validateToken, adminOnly, getAllAgreementLetterHandler);
router.get("/:id/agreement-letter", validateToken, getAgreementLetterByProjectIdHandler);
router.put("/publish", validateToken, adminOnly, uploadDokumenProspektusMiddleware, publishProjectHandler);
router.put("/accept", validateToken, adminOnly, acceptProjectHandler);
router.put("/complete", validateToken, adminOnly, completingProjectHandler);
router.put("/share-profit", validateToken, adminOnly, shareProfitHandler);
router.get("/:id/user", validateToken, getUserHaveTokenInProjectHandler);
router.get("/:id", validateToken, getProjectByIdHandler);
router.put("/approve/:id", validateToken, adminOnly, approveProjectHandler);
router.put("/revise/:id", validateToken, adminOnly, reviseProjectHandler);
router.put("/reject/:id", validateToken, adminOnly, rejectProjectHandler);
router.get("/keterangan-revisi/:id", validateToken, getKeteranganReviseProjectByIdHandler);
router.get("/:id/dokumen-prospektus", validateToken, getDokumenProspektusByIdHandler);
router.get("/total-profit/:id", validateToken, adminOnly, totalProfitHandler);
export default router;

View File

@ -0,0 +1,48 @@
import express from "express";
import {
getTopupByUserIdHandler,
payMemberHandler,
payTopupHandler,
accTopupHandler,
withdrawSaldoHandler,
accWithdrawSaldoHandler,
paySimpananWajibHandler,
accSimpananWajibHandler,
getWithdrawSaldoHandler,
getTopupByIdHandler,
getTotalWalletSaldoByUserIdHandler,
getTotalWalletSimpananWajibByUserIdHandler,
getTotalWalletSimpananPokokByUserIdHandler,
getBagianPemilikPelaksanaHandler,
payBagianPemilikPelaksanaHandler,
getKasKoperasiHandler,
getAllTopupHandler,
getWalletSaldoByUserIdHandler,
updateWalletByIdHandler,
} from "../controllers/topup.js";
import { validateToken, adminOnly } from "../middlewares/auth.js";
import { uploadBuktiMiddleware } from "../middlewares/topup.js";
import validateResource from "../middlewares/validate.resource.js";
import { PayMemberValidation, PaySimpananWajibValidation, PayTopupValidation, WithdrawSaldoValidation } from "../validations/topup.js";
const router = express.Router();
router.get("/", validateToken, adminOnly, getAllTopupHandler);
router.get("/user", validateToken, getTopupByUserIdHandler);
router.get("/simpanan_pokok", validateToken, getTotalWalletSimpananPokokByUserIdHandler);
router.get("/simpanan_wajib", validateToken, getTotalWalletSimpananWajibByUserIdHandler);
router.get("/saldo", validateToken, adminOnly, getWithdrawSaldoHandler);
router.get("/saldo/user", validateToken, getTotalWalletSaldoByUserIdHandler);
router.get("/saldo/user/:id", validateToken, getWalletSaldoByUserIdHandler);
router.get("/kas-koperasi", validateToken, getKasKoperasiHandler);
router.get("/bagian-pemilik-pelaksana", validateToken, adminOnly, getBagianPemilikPelaksanaHandler);
router.get("/:id", validateToken, getTopupByIdHandler);
router.post("/pay-member", validateToken, uploadBuktiMiddleware, validateResource(PayMemberValidation), payMemberHandler);
router.post("/pay-topup", validateToken, uploadBuktiMiddleware, validateResource(PayTopupValidation), payTopupHandler);
router.post("/acc-topup", validateToken, adminOnly, accTopupHandler);
router.post("/withdraw", validateToken, validateResource(WithdrawSaldoValidation), withdrawSaldoHandler);
router.post("/acc-withdraw", validateToken, adminOnly, uploadBuktiMiddleware, accWithdrawSaldoHandler);
router.post("/pay-simpanan-wajib", validateToken, uploadBuktiMiddleware, validateResource(PaySimpananWajibValidation), paySimpananWajibHandler);
router.post("/acc-simpanan-wajib", validateToken, adminOnly, accSimpananWajibHandler);
router.post("/pay-bagian-pemilik-pelaksana", validateToken, adminOnly, uploadBuktiMiddleware, payBagianPemilikPelaksanaHandler);
router.put("/wallet/:id", validateToken, adminOnly, updateWalletByIdHandler);
export default router;

View File

@ -0,0 +1,14 @@
import express from "express";
import {
getAllTransactionHandler,
getTransactionByProjectIdHandler,
getTransactionByUserIdHandler,
} from "../controllers/transaction.js";
import { validateToken } from "../middlewares/auth.js";
const router = express.Router();
router.get("/", validateToken, getAllTransactionHandler);
router.get("/user", validateToken, getTransactionByUserIdHandler);
router.get("/project/:id", validateToken, getTransactionByProjectIdHandler);
export default router;

View File

@ -0,0 +1,34 @@
import express from "express";
import { adminOnly, validateToken } from "../middlewares/auth.js";
import {
accUpgradeUserToPlatinumHandler,
countUserHandler,
deleteUserByIdHandler,
getAllUserHandler,
getPhoneNumberAdminHandler,
getUserByIdHandler,
rejectUserByIdHandler,
sendOtpHandler,
updateUserByIdHandler,
upgradeUserToPlatinumHandler,
verifyOtpHandler,
} from "../controllers/user.js";
import { parseFormData, formDataParserMiddleware } from "../middlewares/auth.js";
import { uploadBuktiMiddleware } from "../middlewares/topup.js";
import validateResource from "../middlewares/validate.resource.js";
import { UpgradePlatinumValidation } from "../validations/user.js";
const router = express.Router();
router.get("/", validateToken, adminOnly, getAllUserHandler);
router.get("/count", validateToken, adminOnly, countUserHandler);
router.get("/phone-number", validateToken, getPhoneNumberAdminHandler);
router.get("/:id", validateToken, getUserByIdHandler);
router.put("/:id", validateToken, parseFormData, formDataParserMiddleware, updateUserByIdHandler);
router.delete("/:id", validateToken, adminOnly, deleteUserByIdHandler);
router.put("/reject/:id", validateToken, adminOnly, rejectUserByIdHandler);
router.post("/send-otp", validateToken, adminOnly, sendOtpHandler);
router.post("/verify-otp", validateToken, verifyOtpHandler);
router.post("/upgrade-platinum", validateToken, uploadBuktiMiddleware, validateResource(UpgradePlatinumValidation), upgradeUserToPlatinumHandler);
router.post("/acc-upgrade-platinum", validateToken, adminOnly, accUpgradeUserToPlatinumHandler);
export default router;

View File

@ -0,0 +1,11 @@
import express from "express";
import * as wilayahController from "../controllers/wilayah.js";
const router = express.Router();
router.get("/provinces", wilayahController.getProvincesHandler);
router.get("/regencies/:province_code", wilayahController.getRegenciesHandler);
router.get("/districts/:regency_code", wilayahController.getDistrictHandler);
router.get("/villages/:district_code", wilayahController.getVillagesHandler);
export default router;

View File

@ -0,0 +1,189 @@
import { db } from "../drizzle/db.js";
import { HistoryProjectTable, ProjectTable, SignatureAdminTable, UserTable } from "../drizzle/schema.js";
import { desc, eq } from "drizzle-orm";
import { getContract } from "../main.js";
export const createAgreementLetter = async (data: any) => {
try {
const contract = await getContract();
// Check if project exists
const project = await db.select().from(ProjectTable).where(eq(ProjectTable.id, data.id_projek)).execute();
if (project.length === 0) {
throw new Error("Project not found");
}
// Get project owner details
const projectOwner = await db.select().from(UserTable).where(eq(UserTable.id, project[0].id_user)).execute();
if (projectOwner.length === 0) {
throw new Error("User associated with the project not found");
}
// Get admin user details
const admin = await db.select().from(UserTable).where(eq(UserTable.role, "ADMIN")).execute();
if (admin.length === 0) {
throw new Error("Admin user not found");
}
// Get the latest admin signature
const adminSignature = await db.select().from(SignatureAdminTable).where(eq(SignatureAdminTable.id_user, admin[0].id)).orderBy(desc(SignatureAdminTable.created_at)).limit(1).execute();
if (adminSignature.length === 0) {
throw new Error("Admin signature not found");
}
// Ensure all values are strings and handle null/undefined values
const params = {
idProjek: data.id_projek || "",
idUser: projectOwner[0].id || "",
namaProyek: project[0].judul || "",
namaPetugas: admin[0].nama || "",
alamatPetugas: admin[0].alamat || "",
namaPemilikProyek: projectOwner[0].nama || "",
nik: projectOwner[0].nik || "",
noHp: projectOwner[0].no_hp || "",
alamat: projectOwner[0].alamat || "",
adminSignature: adminSignature[0].signature || "",
ownerSignature: data.tanda_tangan || "",
nominalDisetujui: project[0].nominal_disetujui || 0,
};
// Create agreement letter with both signatures
const transaction = await contract.createAgreementLetter(
params.idProjek,
params.idUser,
params.namaProyek,
params.namaPetugas,
params.alamatPetugas,
params.namaPemilikProyek,
params.nik,
params.noHp,
params.alamat,
params.adminSignature,
params.ownerSignature,
BigInt(params.nominalDisetujui) // Convert to BigInt for Solidity uint256
);
await transaction.wait();
// Create history entries
await db
.insert(HistoryProjectTable)
.values([
{
id_projek: data.id_projek,
history: "Kontrak Perjanjian",
keterangan: "Kontrak perjanjian sudah ditandatangani oleh pemilik proyek dan admin",
status: "SUCCESS",
},
{
id_projek: data.id_projek,
history: "Proses Penggalangan Penyertaan Modal",
keterangan: "Menunggu proyek dipublish",
status: "PENDING",
},
])
.execute();
return {
status: "success",
message: "Agreement letter created successfully",
data: {
projectId: params.idProjek,
adminSignature: params.adminSignature,
ownerSignature: params.ownerSignature,
timestamp: Date.now(),
},
};
} catch (error) {
console.error("Error creating agreement letter:", error);
throw error; // Re-throw error for controller to handle it
}
};
function formatTimestamp(timestamp: string): string {
try {
const date = new Date(parseInt(timestamp) * 1000);
return date.toISOString();
} catch (error) {
console.error("Error formatting timestamp:", error);
return timestamp;
}
}
export async function getAllAgreementLetter(search?: string) {
let agreementLetter;
try {
const contract = await getContract();
const agreements = await contract.getAllAgreement();
agreementLetter = agreements.map((agreement: any) => ({
idProjek: agreement.idProjek,
idUser: agreement.idUser,
namaProyek: agreement.namaProyek,
namaPetugas: agreement.namaPetugas,
alamatPetugas: agreement.alamatPetugas,
namaPemilikProyek: agreement.namaPemilikProyek,
nik: agreement.nik,
noHp: agreement.noHp,
alamat: agreement.alamat,
signature: agreement.signature,
tandaTangan: agreement.tandaTangan,
nominalDisetujui: agreement.nominalDisetujui.toString(),
createdAt: formatTimestamp(agreement.createdAt.toString()),
}));
// Cek apakah tidak ada agreement letter
if (agreementLetter.length === 0) {
const error = new Error("No agreement letters found");
(error as any).statusCode = 404; // Set statusCode untuk error
throw error; // Lempar error untuk ditangkap di controller
}
} catch (error) {
console.error("Error fetching agreements:", error);
throw error; // Re-throw error agar bisa ditangkap di controller
}
// Filter berdasarkan search query jika ada
if (search) {
agreementLetter = agreementLetter.filter((agreement: any) => agreement.namaPemilikProyek.toLowerCase().includes(search.toLowerCase()));
}
return agreementLetter;
}
export async function getAgreementByProjectId(idProjek: string) {
let projectAgreements;
try {
const contract = await getContract();
projectAgreements = await contract.getAgreementByProjectId(idProjek);
projectAgreements = projectAgreements.map((agreement: any) => ({
idProjek: agreement.idProjek,
idUser: agreement.idUser,
namaProyek: agreement.namaProyek,
namaPetugas: agreement.namaPetugas,
alamatPetugas: agreement.alamatPetugas,
namaPemilikProyek: agreement.namaPemilikProyek,
nik: agreement.nik,
noHp: agreement.noHp,
alamat: agreement.alamat,
signature: agreement.signature,
tandaTangan: agreement.tandaTangan,
nominalDisetujui: agreement.nominalDisetujui.toString(),
createdAt: formatTimestamp(agreement.createdAt.toString()),
}));
} catch (error) {
console.error("Error fetching agreements by project ID:", error);
throw new Error("Error fetching agreements from the smart contract");
}
if (projectAgreements.length === 0) {
const error = new Error("No agreements found for this project ID");
(error as any).statusCode = 404;
throw error;
}
return projectAgreements;
}

View File

@ -0,0 +1,52 @@
import { db } from "../drizzle/db.js";
import { UserTable } from "../drizzle/schema.js";
import bcrypt from "bcryptjs";
import { eq } from "drizzle-orm";
export const register = async (user: any): Promise<{ success: boolean; message: string; statusCode: number }> => {
try {
const tanggalLahir = typeof user.tanggal_lahir === "string" ? user.tanggal_lahir : user.tanggal_lahir.toISOString().split('T')[0];
const hashedPassword = await bcrypt.hash(user.password, 10);
const userData = {
...user,
password: hashedPassword,
tanggal_lahir: tanggalLahir,
};
await db.insert(UserTable).values(userData).execute();
return { success: true, message: "User successfully registered", statusCode: 201 };
} catch (e: any) {
console.error(e);
if (e.code === "23505" && e.detail.includes("Key (no_hp)")) {
return { success: false, message: "Phone number is already in use by another user", statusCode: 409 };
}
return { success: false, message: "User unsuccessfully registered", statusCode: 400 };
}
};
export async function login(no_hp: string, password: string): Promise<any> {
try {
const user = await db.select().from(UserTable).where(eq(UserTable.no_hp, no_hp)).limit(1).execute();
if (!user[0] || !(await bcrypt.compare(password, user[0].password))) {
const error = new Error("Invalid username or password");
(error as any).statusCode = 401;
throw error;
}
return user[0];
} catch (error) {
console.error(error);
if ((error as any).statusCode) {
throw error;
}
const e = new Error("Login failed due to server error");
(e as any).statusCode = 500;
throw e;
}
}

View File

@ -0,0 +1,80 @@
import { makeWASocket, DisconnectReason, useMultiFileAuthState } from "@whiskeysockets/baileys";
import fs from "fs";
let sock: any;
export let qrCode: string | null = null;
export let isConnected: boolean = false;
const startSock = async () => {
const { state, saveCreds } = await useMultiFileAuthState("./auth_baileys");
sock = makeWASocket({
printQRInTerminal: true,
auth: state,
});
sock.ev.on("connection.update", async (update: any) => {
const { connection, lastDisconnect, qr } = update;
if (qr) {
qrCode = qr;
}
if (connection === "close") {
isConnected = false;
const statusCode = (lastDisconnect?.error as any)?.output?.statusCode;
if (statusCode === DisconnectReason.loggedOut) {
console.log("Logged out, resetting auth state...");
await resetAuthState();
await startSock();
} else {
console.log("Connection closed with status code:", statusCode);
await startSock();
}
} else if (connection === "open") {
isConnected = true;
qrCode = null;
console.log("WhatsApp connection opened.");
}
console.log("Connection update:", update);
});
sock.ev.on("creds.update", saveCreds);
sock.ev.on("messages.upsert", async (m: any) => {
const msg = m.messages[0];
if (!msg.key.fromMe && m.type === "notify") {
console.log("Sender Number:", msg.key.remoteJid);
console.log("Message:", msg.message.conversation);
}
});
};
const resetAuthState = async () => {
const authDir = "./auth_baileys";
if (fs.existsSync(authDir)) {
fs.rmSync(authDir, { recursive: true, force: true });
console.log("Auth state reset successfully.");
} else {
console.warn("Auth state directory does not exist.");
}
};
export const sendTextWA = async (number: string, message: string) => {
const formattedNumber = `${number}@s.whatsapp.net`;
try {
if (!isConnected) {
throw new Error("WhatsApp is not connected.");
}
await sock.sendMessage(formattedNumber, { text: message });
console.log(`Message sent to ${number}`);
} catch (error) {
console.error("Failed to send message:", error);
}
};
startSock();

View File

@ -0,0 +1,45 @@
import { eq, sql, sum } from "drizzle-orm";
import { db } from "../drizzle/db.js";
import { ChartProjectTable, ProjectTable, UserTable } from "../drizzle/schema.js";
export async function getChartProjectById(id: string) {
let query = db
.select({chart: ChartProjectTable, project: ProjectTable})
.from(ChartProjectTable)
.innerJoin(ProjectTable, eq(ChartProjectTable.id_projek, ProjectTable.id))
.where(eq(ChartProjectTable.id_projek, id));
const chartProject = await query.execute();
const groupedChartProject = chartProject.reduce((acc, curr) => {
const chartId = curr.chart.id;
if (!acc[chartId]) {
acc[chartId] = {
...curr.chart,
project: curr.project,
};
}
return acc;
}, {} as { [key: string]: any });
return Object.values(groupedChartProject);
}
export async function getAllChartProjectByUserId(id: string) {
const chartProject = await db
.select({
month: sql`extract(month from ${ChartProjectTable.created_at})`.as("month"),
year: sql`extract(year from ${ChartProjectTable.created_at})`.as("year"),
sum_nominal: sum(ChartProjectTable.nominal).as("sum_nominal"),
})
.from(ChartProjectTable)
.innerJoin(ProjectTable, eq(ChartProjectTable.id_projek, ProjectTable.id))
.innerJoin(UserTable, eq(ProjectTable.id_user, UserTable.id))
.where(eq(UserTable.id, id))
.groupBy(sql`extract(year from ${ChartProjectTable.created_at})`, sql`extract(month from ${ChartProjectTable.created_at})`)
.execute();
return chartProject;
}

Some files were not shown because too many files have changed in this diff Show More