initial commit
This commit is contained in:
commit
83474a2539
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
/.idea
|
||||||
|
/.vscode
|
||||||
|
/node_modules
|
||||||
25
.gitlab-ci.yml
Normal file
25
.gitlab-ci.yml
Normal 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
173
README.md
Normal 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
0
api-docs/README.md
Normal file
BIN
api-docs/postman-koperasi-blockchain.zip
Normal file
BIN
api-docs/postman-koperasi-blockchain.zip
Normal file
Binary file not shown.
3403
api-docs/swagger-output.json
Normal file
3403
api-docs/swagger-output.json
Normal file
File diff suppressed because it is too large
Load Diff
4
env-init.sh
Normal file
4
env-init.sh
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Your initialization script commands here
|
||||||
|
cd ./services/backend && cp .env.example .env
|
||||||
2
install.sh
Normal file
2
install.sh
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
cd ./services/frontend && npm install
|
||||||
|
cd ./services/backend && npm install
|
||||||
22
services/backend/.env.example
Normal file
22
services/backend/.env.example
Normal 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
36
services/backend/.gitignore
vendored
Normal 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
21
services/backend/LICENSE
Normal 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.
|
||||||
53
services/backend/README.md
Normal file
53
services/backend/README.md
Normal 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
|
||||||
|
```
|
||||||
BIN
services/backend/assets/statis/formatan-dokumen-proyeksi.docx
Normal file
BIN
services/backend/assets/statis/formatan-dokumen-proyeksi.docx
Normal file
Binary file not shown.
0
services/backend/assets/uploads/foto_diri/.gitkeep
Normal file
0
services/backend/assets/uploads/foto_diri/.gitkeep
Normal file
0
services/backend/assets/uploads/foto_ktp/.gitkeep
Normal file
0
services/backend/assets/uploads/foto_ktp/.gitkeep
Normal file
2031
services/backend/contractABI.json
Normal file
2031
services/backend/contractABI.json
Normal file
File diff suppressed because it is too large
Load Diff
12
services/backend/drizzle.config.ts
Normal file
12
services/backend/drizzle.config.ts
Normal 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
6
services/backend/express.d.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
declare namespace Express {
|
||||||
|
interface Request {
|
||||||
|
user?: any;
|
||||||
|
query?: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
4869
services/backend/package-lock.json
generated
Normal file
4869
services/backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
65
services/backend/package.json
Normal file
65
services/backend/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
56
services/backend/src/controllers/agreement-letter.ts
Normal file
56
services/backend/src/controllers/agreement-letter.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
};
|
||||||
63
services/backend/src/controllers/auth.ts
Normal file
63
services/backend/src/controllers/auth.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
23
services/backend/src/controllers/baileys.ts
Normal file
23
services/backend/src/controllers/baileys.ts
Normal 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.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
32
services/backend/src/controllers/chart-project.ts
Normal file
32
services/backend/src/controllers/chart-project.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
};
|
||||||
50
services/backend/src/controllers/chart-token.ts
Normal file
50
services/backend/src/controllers/chart-token.ts
Normal 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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
15
services/backend/src/controllers/history-project-wallet.ts
Normal file
15
services/backend/src/controllers/history-project-wallet.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
13
services/backend/src/controllers/history-project.ts
Normal file
13
services/backend/src/controllers/history-project.ts
Normal 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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
20
services/backend/src/controllers/history-token.ts
Normal file
20
services/backend/src/controllers/history-token.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
54
services/backend/src/controllers/mutation.ts
Normal file
54
services/backend/src/controllers/mutation.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
70
services/backend/src/controllers/project-category.ts
Normal file
70
services/backend/src/controllers/project-category.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
};
|
||||||
63
services/backend/src/controllers/project-report.ts
Normal file
63
services/backend/src/controllers/project-report.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
};
|
||||||
124
services/backend/src/controllers/project-token.ts
Normal file
124
services/backend/src/controllers/project-token.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
42
services/backend/src/controllers/project-wallet.ts
Normal file
42
services/backend/src/controllers/project-wallet.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
247
services/backend/src/controllers/project.ts
Normal file
247
services/backend/src/controllers/project.ts
Normal 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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
272
services/backend/src/controllers/topup.ts
Normal file
272
services/backend/src/controllers/topup.ts
Normal 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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
57
services/backend/src/controllers/transaction.ts
Normal file
57
services/backend/src/controllers/transaction.ts
Normal 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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
148
services/backend/src/controllers/user.ts
Normal file
148
services/backend/src/controllers/user.ts
Normal 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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
84
services/backend/src/controllers/wilayah.ts
Normal file
84
services/backend/src/controllers/wilayah.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
};
|
||||||
15
services/backend/src/drizzle/db.ts
Normal file
15
services/backend/src/drizzle/db.ts
Normal 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);
|
||||||
16
services/backend/src/drizzle/migrate.ts
Normal file
16
services/backend/src/drizzle/migrate.ts
Normal 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()
|
||||||
270
services/backend/src/drizzle/schema.ts
Normal file
270
services/backend/src/drizzle/schema.ts
Normal 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],
|
||||||
|
}),
|
||||||
|
}));
|
||||||
142
services/backend/src/drizzle/seed.ts
Normal file
142
services/backend/src/drizzle/seed.ts
Normal 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();
|
||||||
52
services/backend/src/drizzle/seeder/agreement-letter.ts
Normal file
52
services/backend/src/drizzle/seeder/agreement-letter.ts
Normal 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);
|
||||||
|
};
|
||||||
49
services/backend/src/drizzle/seeder/chart-project.ts
Normal file
49
services/backend/src/drizzle/seeder/chart-project.ts
Normal 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);
|
||||||
|
};
|
||||||
55
services/backend/src/drizzle/seeder/chart-token.ts
Normal file
55
services/backend/src/drizzle/seeder/chart-token.ts
Normal 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);
|
||||||
|
};
|
||||||
|
|
@ -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 };
|
||||||
|
};
|
||||||
33
services/backend/src/drizzle/seeder/history-project.ts
Normal file
33
services/backend/src/drizzle/seeder/history-project.ts
Normal 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);
|
||||||
|
};
|
||||||
39
services/backend/src/drizzle/seeder/history-token.ts
Normal file
39
services/backend/src/drizzle/seeder/history-token.ts
Normal 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(),
|
||||||
|
},
|
||||||
|
];
|
||||||
70
services/backend/src/drizzle/seeder/project-mutation.ts
Normal file
70
services/backend/src/drizzle/seeder/project-mutation.ts
Normal 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);
|
||||||
|
};
|
||||||
71
services/backend/src/drizzle/seeder/project-report.ts
Normal file
71
services/backend/src/drizzle/seeder/project-report.ts
Normal 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);
|
||||||
|
};
|
||||||
31
services/backend/src/drizzle/seeder/project-wallet.ts
Normal file
31
services/backend/src/drizzle/seeder/project-wallet.ts
Normal 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 };
|
||||||
|
};
|
||||||
125
services/backend/src/drizzle/seeder/project.ts
Normal file
125
services/backend/src/drizzle/seeder/project.ts
Normal 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);
|
||||||
|
};
|
||||||
9
services/backend/src/drizzle/seeder/signature-admin.ts
Normal file
9
services/backend/src/drizzle/seeder/signature-admin.ts
Normal 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(),
|
||||||
|
},
|
||||||
|
];
|
||||||
33
services/backend/src/drizzle/seeder/token.ts
Normal file
33
services/backend/src/drizzle/seeder/token.ts
Normal 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);
|
||||||
|
};
|
||||||
101
services/backend/src/drizzle/seeder/topup.ts
Normal file
101
services/backend/src/drizzle/seeder/topup.ts
Normal 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);
|
||||||
|
};
|
||||||
37
services/backend/src/drizzle/seeder/transaction.ts
Normal file
37
services/backend/src/drizzle/seeder/transaction.ts
Normal 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(),
|
||||||
|
}
|
||||||
|
];
|
||||||
91
services/backend/src/drizzle/seeder/user.ts
Normal file
91
services/backend/src/drizzle/seeder/user.ts
Normal 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);
|
||||||
|
};
|
||||||
89
services/backend/src/drizzle/seeder/wallet.ts
Normal file
89
services/backend/src/drizzle/seeder/wallet.ts
Normal 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);
|
||||||
|
};
|
||||||
119
services/backend/src/main.ts
Normal file
119
services/backend/src/main.ts
Normal 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}`);
|
||||||
|
});
|
||||||
40
services/backend/src/middlewares/agreement-letter.ts
Normal file
40
services/backend/src/middlewares/agreement-letter.ts
Normal 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();
|
||||||
|
});
|
||||||
|
};
|
||||||
15
services/backend/src/middlewares/api-key.ts
Normal file
15
services/backend/src/middlewares/api-key.ts
Normal 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);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
110
services/backend/src/middlewares/auth.ts
Normal file
110
services/backend/src/middlewares/auth.ts
Normal 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" });
|
||||||
|
}
|
||||||
|
};
|
||||||
34
services/backend/src/middlewares/history-project-wallet.ts
Normal file
34
services/backend/src/middlewares/history-project-wallet.ts
Normal 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();
|
||||||
|
});
|
||||||
|
};
|
||||||
33
services/backend/src/middlewares/mutation.ts
Normal file
33
services/backend/src/middlewares/mutation.ts
Normal 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();
|
||||||
|
});
|
||||||
|
};
|
||||||
33
services/backend/src/middlewares/project-report.ts
Normal file
33
services/backend/src/middlewares/project-report.ts
Normal 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();
|
||||||
|
});
|
||||||
|
};
|
||||||
150
services/backend/src/middlewares/project.ts
Normal file
150
services/backend/src/middlewares/project.ts
Normal 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();
|
||||||
|
});
|
||||||
|
};
|
||||||
34
services/backend/src/middlewares/topup.ts
Normal file
34
services/backend/src/middlewares/topup.ts
Normal 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();
|
||||||
|
});
|
||||||
|
};
|
||||||
24
services/backend/src/middlewares/validate.resource.ts
Normal file
24
services/backend/src/middlewares/validate.resource.ts
Normal 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;
|
||||||
13
services/backend/src/routes/auth.ts
Normal file
13
services/backend/src/routes/auth.ts
Normal 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;
|
||||||
11
services/backend/src/routes/baileys.ts
Normal file
11
services/backend/src/routes/baileys.ts
Normal 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;
|
||||||
9
services/backend/src/routes/chart-project.ts
Normal file
9
services/backend/src/routes/chart-project.ts
Normal 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;
|
||||||
10
services/backend/src/routes/chart-token.ts
Normal file
10
services/backend/src/routes/chart-token.ts
Normal 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;
|
||||||
8
services/backend/src/routes/history-project-wallet.ts
Normal file
8
services/backend/src/routes/history-project-wallet.ts
Normal 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;
|
||||||
8
services/backend/src/routes/history-project.ts
Normal file
8
services/backend/src/routes/history-project.ts
Normal 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;
|
||||||
8
services/backend/src/routes/history-token.ts
Normal file
8
services/backend/src/routes/history-token.ts
Normal 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;
|
||||||
14
services/backend/src/routes/mutation.ts
Normal file
14
services/backend/src/routes/mutation.ts
Normal 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;
|
||||||
15
services/backend/src/routes/project-category.ts
Normal file
15
services/backend/src/routes/project-category.ts
Normal 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;
|
||||||
15
services/backend/src/routes/project-report.ts
Normal file
15
services/backend/src/routes/project-report.ts
Normal 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;
|
||||||
18
services/backend/src/routes/project-token.ts
Normal file
18
services/backend/src/routes/project-token.ts
Normal 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;
|
||||||
14
services/backend/src/routes/project-wallet.ts
Normal file
14
services/backend/src/routes/project-wallet.ts
Normal 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;
|
||||||
53
services/backend/src/routes/project.ts
Normal file
53
services/backend/src/routes/project.ts
Normal 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;
|
||||||
48
services/backend/src/routes/topup.ts
Normal file
48
services/backend/src/routes/topup.ts
Normal 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;
|
||||||
14
services/backend/src/routes/transaction.ts
Normal file
14
services/backend/src/routes/transaction.ts
Normal 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;
|
||||||
34
services/backend/src/routes/user.ts
Normal file
34
services/backend/src/routes/user.ts
Normal 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;
|
||||||
11
services/backend/src/routes/wilayah.ts
Normal file
11
services/backend/src/routes/wilayah.ts
Normal 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;
|
||||||
189
services/backend/src/services/agreement-letter.ts
Normal file
189
services/backend/src/services/agreement-letter.ts
Normal 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;
|
||||||
|
}
|
||||||
52
services/backend/src/services/auth.ts
Normal file
52
services/backend/src/services/auth.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
80
services/backend/src/services/baileys.ts
Normal file
80
services/backend/src/services/baileys.ts
Normal 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();
|
||||||
45
services/backend/src/services/chart-project.ts
Normal file
45
services/backend/src/services/chart-project.ts
Normal 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
Loading…
Reference in New Issue
Block a user