From 8b54f5ca6572cc29a9ec52b15939141b8347fd82 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Thu, 29 Jan 2026 15:04:40 +0800 Subject: [PATCH 1/4] Fix send whatsapp Auth API - src/app/api/auth/login/route.ts - src/app/api/auth/mobile-login/route.ts - src/app/api/auth/mobile-register/route.ts - src/app/api/auth/resend/route.ts User API (Mobile) - src/app/api/mobile/user/route.ts - src/app/api/mobile/admin/user/[id]/route.ts Utility - src/lib/code-otp-sender.ts ### No issue --- src/app/api/auth/login/route.ts | 4 ++-- src/app/api/auth/mobile-login/route.ts | 4 ++-- src/app/api/auth/mobile-register/route.ts | 4 ++-- src/app/api/auth/resend/route.ts | 4 ++-- src/app/api/mobile/admin/user/[id]/route.ts | 18 ++++++++++++++ src/app/api/mobile/user/route.ts | 26 ++++++++++++--------- src/lib/code-otp-sender.ts | 11 +++++---- 7 files changed, 48 insertions(+), 23 deletions(-) diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts index a53bc710..c9e26835 100644 --- a/src/app/api/auth/login/route.ts +++ b/src/app/api/auth/login/route.ts @@ -2,7 +2,7 @@ import { prisma } from "@/lib"; import { randomOTP } from "@/app_modules/auth/fun/rondom_otp"; import backendLogger from "@/util/backendLogger"; import { NextResponse } from "next/server"; -import { sendCodeOtp } from "@/lib/code-otp-sender"; +import { funSendToWhatsApp } from "@/lib/code-otp-sender"; export async function POST(req: Request) { if (req.method !== "POST") { @@ -30,7 +30,7 @@ export async function POST(req: Request) { { status: 400 }, ); - const resSendCode = await sendCodeOtp({ + const resSendCode = await funSendToWhatsApp({ nomor, codeOtp: codeOtp.toString(), }); diff --git a/src/app/api/auth/mobile-login/route.ts b/src/app/api/auth/mobile-login/route.ts index 7b1c4847..ea49ad24 100644 --- a/src/app/api/auth/mobile-login/route.ts +++ b/src/app/api/auth/mobile-login/route.ts @@ -1,7 +1,7 @@ import { prisma } from "@/lib"; import { randomOTP } from "@/app_modules/auth/fun/rondom_otp"; import { NextResponse } from "next/server"; -import { sendCodeOtp } from "@/lib/code-otp-sender"; +import { funSendToWhatsApp } from "@/lib/code-otp-sender"; export async function POST(req: Request) { try { @@ -35,7 +35,7 @@ export async function POST(req: Request) { { status: 400 }, ); - const resSendCode = await sendCodeOtp({ + const resSendCode = await funSendToWhatsApp({ nomor, codeOtp: codeOtp.toString(), }); diff --git a/src/app/api/auth/mobile-register/route.ts b/src/app/api/auth/mobile-register/route.ts index 9f3b74e0..2580fd9d 100644 --- a/src/app/api/auth/mobile-register/route.ts +++ b/src/app/api/auth/mobile-register/route.ts @@ -7,7 +7,7 @@ import { NotificationMobileBodyType, NotificationMobileTitleType, } from "../../../../../types/type-mobile-notification"; -import { sendCodeOtp } from "@/lib/code-otp-sender"; +import { funSendToWhatsApp } from "@/lib/code-otp-sender"; export async function POST(req: Request) { if (req.method !== "POST") { @@ -70,7 +70,7 @@ export async function POST(req: Request) { { status: 400 } ); - const resSendCode = await sendCodeOtp({ + const resSendCode = await funSendToWhatsApp({ nomor: data.nomor, codeOtp: codeOtp.toString(), }); diff --git a/src/app/api/auth/resend/route.ts b/src/app/api/auth/resend/route.ts index eb64a8f5..adc6491c 100644 --- a/src/app/api/auth/resend/route.ts +++ b/src/app/api/auth/resend/route.ts @@ -2,7 +2,7 @@ import { prisma } from "@/lib"; import { randomOTP } from "@/app_modules/auth/fun/rondom_otp"; import backendLogger from "@/util/backendLogger"; import { NextResponse } from "next/server"; -import { sendCodeOtp } from "@/lib/code-otp-sender"; +import { funSendToWhatsApp } from "@/lib/code-otp-sender"; export async function POST(req: Request) { if (req.method !== "POST") { @@ -17,7 +17,7 @@ export async function POST(req: Request) { const body = await req.json(); const { nomor } = body; - const resSendCode = await sendCodeOtp({ + const resSendCode = await funSendToWhatsApp({ nomor, codeOtp: codeOtp.toString(), }); diff --git a/src/app/api/mobile/admin/user/[id]/route.ts b/src/app/api/mobile/admin/user/[id]/route.ts index 5c58248d..b2b03824 100644 --- a/src/app/api/mobile/admin/user/[id]/route.ts +++ b/src/app/api/mobile/admin/user/[id]/route.ts @@ -1,4 +1,5 @@ import { prisma } from "@/lib"; +import { funSendToWhatsApp } from "@/lib/code-otp-sender"; import _ from "lodash"; import { NextResponse } from "next/server"; @@ -50,8 +51,25 @@ async function PUT(request: Request, { params }: { params: { id: string } }) { data: { active: data.active, }, + select: { + nomor: true, + }, }); + if (data.active) { + const resSendCode = await funSendToWhatsApp({ + nomor: updateData.nomor, + newMessage: + "Halo sahabat HIConnect, \nSelamat akun anda telah aktif ! \n\n*Pesan ini di kirim secara otomatis, tidak perlu di balas.", + }); + } else { + const resSendCode = await funSendToWhatsApp({ + nomor: updateData.nomor, + newMessage: + "Halo sahabat HIConnect, \nMohon maaf akun anda telah dinonaktifkan ! Hubungi admin untuk informasi lebih lanjut. \n\n*Pesan ini di kirim secara otomatis, tidak perlu di balas.", + }); + } + console.log("[Update Active Berhasil]", updateData); } else if (category === "role") { const fixName = _.startCase(data.role.replace(/_/g, " ")); diff --git a/src/app/api/mobile/user/route.ts b/src/app/api/mobile/user/route.ts index 3a414098..e84faf27 100644 --- a/src/app/api/mobile/user/route.ts +++ b/src/app/api/mobile/user/route.ts @@ -5,8 +5,16 @@ export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); const search = searchParams.get("search"); + const page = Number(searchParams.get("page")); + const takeData = 10; + const skipData = page * takeData - takeData; + + console.log("SEARCH", search); + console.log("PAGE", page); const data = await prisma.user.findMany({ + take: page ? takeData : undefined, + skip: page ? skipData : undefined, orderBy: { username: "asc", }, @@ -43,16 +51,12 @@ export async function GET(request: Request) { }, }); - return NextResponse.json( - { - success: true, - message: "Berhasil mendapatkan data", - data: data, - }, - { - status: 200, - } - ); + return NextResponse.json({ + status: 200, + success: true, + message: "Berhasil mendapatkan data", + data: data, + }); } catch (error) { return NextResponse.json( { @@ -62,7 +66,7 @@ export async function GET(request: Request) { }, { status: 500, - } + }, ); } diff --git a/src/lib/code-otp-sender.ts b/src/lib/code-otp-sender.ts index 134e20b5..30376c21 100644 --- a/src/lib/code-otp-sender.ts +++ b/src/lib/code-otp-sender.ts @@ -1,13 +1,16 @@ const sendCodeOtp = async ({ nomor, codeOtp, + newMessage, }: { nomor: string; - codeOtp: string; + codeOtp?: string; + newMessage?: string; }) => { - const msg = `HIPMI%20-%20Kode%20ini%20bersifat%20RAHASIA%20dan%20JANGAN%20DI%20BAGIKAN%20KEPADA%20SIAPAPUN%2C%20termasuk%20anggota%20ataupun%20pengurus%20HIPMI%20lainnya.%20Kode%20OTP%20anda%3A%20${codeOtp}.`; + const msg = newMessage || `HIPMI - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPADA SIAPAPUN, termasuk anggota ataupun pengurus HIPMI lainnya.\n\n>> Kode OTP anda: ${codeOtp}.`; + const enCode = encodeURIComponent(msg); const res = await fetch( - `https://cld-dkr-prod-wajs-server.wibudev.com/api/wa/code?nom=${nomor}&text=${msg}`, + `https://cld-dkr-prod-wajs-server.wibudev.com/api/wa/code?nom=${nomor}&text=${enCode}`, { cache: "no-cache", headers: { @@ -25,4 +28,4 @@ const sendCodeOtp = async ({ return res; }; -export { sendCodeOtp }; +export { sendCodeOtp as funSendToWhatsApp }; From 1c9459dcf397502727c1df9928a0faa7f8af2a8b Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Thu, 29 Jan 2026 17:41:21 +0800 Subject: [PATCH 2/4] Fix comment forum Forum API (Mobile) - src/app/api/mobile/forum/[id]/comment/route.ts ### No Issue --- src/app/api/mobile/forum/[id]/comment/route.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/app/api/mobile/forum/[id]/comment/route.ts b/src/app/api/mobile/forum/[id]/comment/route.ts index 04269853..c7725e03 100644 --- a/src/app/api/mobile/forum/[id]/comment/route.ts +++ b/src/app/api/mobile/forum/[id]/comment/route.ts @@ -90,9 +90,15 @@ async function POST(request: Request, { params }: { params: { id: string } }) { async function GET(request: Request, { params }: { params: { id: string } }) { const { id } = params; + const { searchParams } = new URL(request.url); + const page = Number(searchParams.get("page")); + const takeData = 5 + const skipData = page * takeData - takeData; try { const data = await prisma.forum_Komentar.findMany({ + take: page ? takeData : undefined, + skip: page ? skipData : undefined, orderBy: { createdAt: "desc", }, From f103ae93ad2d2badcaa76af79d98ca0ee71e9ca5 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Fri, 30 Jan 2026 17:16:05 +0800 Subject: [PATCH 3/4] chore(release): 1.5.39 --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42084ebd..e8c04100 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines. +## [1.5.39](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.38...v1.5.39) (2026-01-30) + ## [1.5.38](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.37...v1.5.38) (2026-01-27) ## [1.5.37](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.36...v1.5.37) (2026-01-23) diff --git a/package.json b/package.json index 6827a738..76c8178f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hipmi", - "version": "1.5.38", + "version": "1.5.39", "private": true, "prisma": { "seed": "bun prisma/seed.ts" From bb79a68f44e920211f3629dba70fefb9c0d10414 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Fri, 30 Jan 2026 17:16:33 +0800 Subject: [PATCH 4/4] =?UTF-8?q?API=20=E2=80=93=20Mobile=20Notification=20-?= =?UTF-8?q?=20src/app/api/mobile/notification/[id]/route.ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API – Portofolio (Mobile) - src/app/api/mobile/portofolio/route.ts Untracked Files - PROMPT-AI.md - QWEN.md ### No Issue --- PROMPT-AI.md | 16 ++ QWEN.md | 201 ++++++++++++++++++ src/app/api/mobile/notification/[id]/route.ts | 38 +++- src/app/api/mobile/portofolio/route.ts | 48 +++-- 4 files changed, 285 insertions(+), 18 deletions(-) create mode 100644 PROMPT-AI.md create mode 100644 QWEN.md diff --git a/PROMPT-AI.md b/PROMPT-AI.md new file mode 100644 index 00000000..c3a14f44 --- /dev/null +++ b/PROMPT-AI.md @@ -0,0 +1,16 @@ + +File utama: src/app/api/mobile/notification/[id]/route.ts + +Terapkan pagination pada file "File utama" pada method GET +Analisa juga file "File utama", jika belum memiliki page dari seachParams maka terapkan. Juga pastikan take dan skip sudah sesuai dengan pagination. Buat default nya menjadi 10 untuk take data + +Contoh: +const page = Number(searchParams.get("page")); +const takeData = 10; +const skipData = page * takeData - takeData; + +dan penerapannya pada query +take: page ? takeData : undefined, +skip: page ? skipData : undefined, + +Gunakan bahasa indonesia pada cli agar saya mudah membacanya. diff --git a/QWEN.md b/QWEN.md new file mode 100644 index 00000000..57dfe1b8 --- /dev/null +++ b/QWEN.md @@ -0,0 +1,201 @@ +# HIPMI Project - QWEN.md + +## Project Overview + +HIPMI (Himpunan Pengusaha Muda Indonesia) is a comprehensive Next.js-based web application built for the Indonesian Young Entrepreneurs Association. The project is a sophisticated platform that provides multiple business functionalities including investment management, donations, events, job listings, forums, voting systems, and collaborative projects. + +### Key Technologies +- **Framework**: Next.js 13+ (with App Router) +- **Language**: TypeScript +- **Database**: PostgreSQL with Prisma ORM +- **Styling**: Tailwind CSS with Mantine UI components +- **Authentication**: JWT-based with custom middleware +- **Runtime**: Bun (instead of Node.js) +- **Deployment**: Standalone output configuration + +### Team Structure +- **bagas**: Frontend, DevOps +- **lukman**: Frontend, UI +- **lia**: Backend, Frontend, QC +- **malik**: Leader + +## Architecture & Features + +### Core Modules +The application is organized into several major functional areas: + +1. **Authentication System** (`/auth`) - Login, registration, validation +2. **Investment Platform** (`/investasi`) - Investment creation, trading, portfolio management +3. **Donation System** (`/donasi`) - Fundraising campaigns, donation processing +4. **Event Management** (`/event`) - Event creation, participation, management +5. **Voting System** (`/vote`) - Voting creation and participation +6. **Job Board** (`/job`) - Job posting and application system +7. **Forum** (`/forum`) - Discussion platform with reporting features +8. **Collaboration Platform** (`/colab`) - Project collaboration tools +9. **User Profiles** (`/profile`) - User profile management +10. **Business Maps** (`/maps`) - Business location mapping + +### Admin Panel +The application includes a comprehensive admin panel (`/admin`) with modules for managing: +- Investment approvals and transfers +- Donation campaign reviews +- Event management +- Forum moderation +- Job posting reviews +- User access management +- Voting oversight + +### Technical Architecture + +#### Routing & Authentication +- Custom middleware handles authentication and authorization +- Public routes are defined in the middleware configuration +- JWT tokens are stored in cookies with secure options +- Role-based access control (MasterUserRole model) + +#### Database Schema +The Prisma schema defines a comprehensive data model with: +- User management with roles and profiles +- Investment and financial systems +- Donation and crowdfunding features +- Event management +- Forum and discussion systems +- Collaboration platforms +- Notification systems +- Business mapping features + +#### Environment Configuration +The application uses multiple environment files for different deployment stages: +- Development: `run.env.dev`, `run.env.local.dev` +- Build: `run.env.build.dev`, `run.env.build.local` +- Runtime: `run.env.start.dev`, `run.env.start.local` + +## Building and Running + +### Prerequisites +- Bun runtime installed +- PostgreSQL database +- Required environment variables configured + +### Setup Commands +```bash +# Install dependencies +bun install + +# Setup database (Prisma) +bun prisma generate +bun prisma db push +bun prisma db seed + +# Development server +bun run dev + +# Build for production +bun run build + +# Start production server +bun run start +``` + +### Development Scripts +- `dev`: Starts development server with HTTPS +- `build`: Builds the application for production +- `start`: Starts the production server +- `lint`: Runs Next.js linting +- `ver`: Creates version tags + +## Development Conventions + +### Git Workflow +The team follows a structured Git workflow: +1. Check status with `git status` +2. Add specific files with `git add ` (avoid `git add -A`) +3. Commit with structured messages following conventional commits: + - `feat`: New features + - `fix`: Bug fixes + - `docs`: Documentation updates + - `chore`: Routine tasks + - `refactor`: Code restructuring + - `test`: Testing additions + - `style`: Styling changes + - `perf`: Performance improvements + +### Code Standards +- TypeScript with strict mode enabled +- Component header comments with file description, creator, date +- Function comments with parameter and return value descriptions +- Custom type interface comments +- Error handling comments +- Complex logic comments + +### Commit Message Format +``` +type: Short description + +Body: +- Detailed description of changes +- Motivation for changes +- Breaking changes if any + +References: #issue-number +``` + +## Project Structure + +### Main Directories +- `src/app`: Next.js App Router pages and layouts +- `src/app_modules`: Reusable application modules +- `src/lib`: Shared libraries and utilities +- `src/util`: Utility functions +- `prisma`: Database schema and migrations +- `public`: Static assets +- `certificates`, `logs`: Application data directories + +### Key Files +- `package.json`: Dependencies and scripts +- `next.config.js`: Next.js configuration +- `middleware.tsx`: Authentication and routing middleware +- `tsconfig.json`: TypeScript configuration +- `tailwind.config.js`: Styling configuration +- `gen_page.tsx`: Generated page routing utility +- `prisma/schema.prisma`: Database schema definition + +## Special Features + +### Real-time Capabilities +- WebSocket integration for real-time updates +- MQTT support for messaging +- Live notifications system + +### Payment Integration +- Midtrans payment gateway for investments +- Multiple payment methods +- Invoice generation and tracking + +### File Handling +- PDF generation and viewing +- Image processing and storage +- Document management for investments + +### Mobile Support +- Responsive design +- Mobile-specific features +- Device token management + +## Security Measures + +### Authentication +- JWT-based authentication +- Session management +- Role-based access control +- Secure token storage in cookies + +### Input Validation +- Prisma schema validations +- Server-side validation +- Sanitization of user inputs + +### Data Protection +- Encrypted tokens +- Secure API routes +- Proper CORS configuration \ No newline at end of file diff --git a/src/app/api/mobile/notification/[id]/route.ts b/src/app/api/mobile/notification/[id]/route.ts index 0a3275b6..8844419d 100644 --- a/src/app/api/mobile/notification/[id]/route.ts +++ b/src/app/api/mobile/notification/[id]/route.ts @@ -16,7 +16,13 @@ export async function GET( const fixCategory = _.upperCase(category || ""); try { + const page = Number(searchParams.get("page")); + const takeData = 10; + const skipData = page ? page * takeData - takeData : 0; + const data = await prisma.notifikasi.findMany({ + take: page ? takeData : undefined, + skip: page ? skipData : undefined, orderBy: { createdAt: "desc", }, @@ -26,12 +32,40 @@ export async function GET( }, }); + // Jika pagination digunakan, ambil juga total count untuk informasi + let totalCount; + let totalPages; + if (page) { + totalCount = await prisma.notifikasi.count({ + where: { + recipientId: id, + kategoriApp: fixCategory, + }, + }); + totalPages = Math.ceil(totalCount / takeData); + } + fixData = data; - return NextResponse.json({ + const response = { success: true, data: fixData, - }); + }; + + // Tambahkan metadata pagination jika parameter page disertakan + if (page) { + Object.assign(response, { + meta: { + page, + take: takeData, + skip: skipData, + total: totalCount, + totalPages, + }, + }); + } + + return NextResponse.json(response); } catch (error) { return NextResponse.json( { error: (error as Error).message }, diff --git a/src/app/api/mobile/portofolio/route.ts b/src/app/api/mobile/portofolio/route.ts index b542f341..893c72dc 100644 --- a/src/app/api/mobile/portofolio/route.ts +++ b/src/app/api/mobile/portofolio/route.ts @@ -7,8 +7,13 @@ async function GET(request: Request, { params }: { params: { id: string } }) { try { const { searchParams } = new URL(request.url); const id = searchParams.get("id"); + const page = parseInt(searchParams.get("page") || "1"); + const take = 10; // Default 10 data + const skip = page * take - take; const data = await prisma.portofolio.findMany({ + skip, + take, orderBy: { createdAt: "desc", }, @@ -18,22 +23,30 @@ async function GET(request: Request, { params }: { params: { id: string } }) { }, }); - if (!data) - return NextResponse.json( - { - success: false, - message: "Data tidak ditemukan", - }, - { status: 404 } - ); + // Hitung total data untuk informasi pagination + const total = await prisma.portofolio.count({ + where: { + profileId: id, + active: true, + }, + }); + + const totalPages = Math.ceil(total / take); return NextResponse.json( { success: true, message: "Berhasil mendapatkan data", data: data, + meta: { + page, + take, + skip, + total, + totalPages, + }, }, - { status: 200 } + { status: 200 }, ); } catch (error) { return NextResponse.json( @@ -42,7 +55,7 @@ async function GET(request: Request, { params }: { params: { id: string } }) { message: "API Error Get Data Potofolio", reason: (error as Error).message, }, - { status: 500 } + { status: 500 }, ); } } @@ -66,7 +79,10 @@ async function POST(request: Request) { }, }); - if (data.subBidang.length > 0 || data.subBidang.map((item: any) => item.id !== "")) { + if ( + data.subBidang.length > 0 || + data.subBidang.map((item: any) => item.id !== "") + ) { for (let i of data.subBidang) { const createSubBidang = await prisma.portofolio_BidangDanSubBidangBisnis.create({ @@ -84,7 +100,7 @@ async function POST(request: Request) { success: false, message: "Gagal membuat sub bidang bisnis", }, - { status: 400 } + { status: 400 }, ); } } @@ -95,7 +111,7 @@ async function POST(request: Request) { success: false, message: "Gagal membuat portofolio", }, - { status: 400 } + { status: 400 }, ); const createMedsos = await prisma.portofolio_MediaSosial.create({ @@ -115,7 +131,7 @@ async function POST(request: Request) { success: false, message: "Gagal menambahkan medsos", }, - { status: 400 } + { status: 400 }, ); return NextResponse.json( @@ -124,7 +140,7 @@ async function POST(request: Request) { message: "Berhasil mendapatkan data", data: createPortofolio, }, - { status: 200 } + { status: 200 }, ); } catch (error) { return NextResponse.json( @@ -133,7 +149,7 @@ async function POST(request: Request) { message: "API Error Post Data", reason: (error as Error).message, }, - { status: 500 } + { status: 500 }, ); } }