From 3e6c94d77f202e067ac1bcded0f488dec6cc9dbb Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Thu, 5 Mar 2026 14:28:45 +0800 Subject: [PATCH 1/3] Usulan Commit Message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: Implementasi retry mechanism dan error handling untuk database connections Deskripsi: Menambahkan withRetry wrapper pada berbagai API routes untuk menangani transient database errors dan meningkatkan reliabilitas koneksi Memperbaiki error handling pada notification, authentication, dan user validation endpoints dengan response 503 untuk database connection errors Update prisma.ts dengan konfigurasi logging yang lebih baik dan datasources configuration Menambahkan validasi input parameters pada beberapa endpoints Update dokumentasi QWEN.md dengan commit message format dan comment standards Update .env.example dengan connection pool settings yang lebih lengkap File yang diubah: src/lib/prisma.ts — Konfigurasi Prisma client & logging src/app/api/admin/notifikasi/count/route.tsx src/app/api/auth/mobile-login/route.ts src/app/api/mobile/notification/[id]/route.ts src/app/api/user-validate/route.ts Dan 27 file API routes lainnya (penerapan withRetry secara konsisten) QWEN.md — Dokumentasi commit & comment standards .env.example — Database connection pool configuration ### No Issue --- .env.example | 7 +- QWEN.md | 64 ++++++++++++++++--- prisma/seed.ts | 6 +- src/app/api/admin/notifikasi/count/route.tsx | 45 +++++++++++-- src/app/api/auth/code/[id]/route.ts | 2 - src/app/api/auth/mobile-login/route.ts | 62 ++++++++++++++---- src/app/api/auth/resend/route.ts | 2 - src/app/api/event/[id]/route.ts | 2 - src/app/api/event/check-peserta/route.ts | 2 - src/app/api/event/sponsor/[id]/route.ts | 4 -- src/app/api/event/sponsor/list/[id]/route.ts | 2 - src/app/api/event/tipe-acara/route.ts | 2 - src/app/api/master/bank/route.ts | 2 - src/app/api/master/status_transaksi/route.ts | 2 - src/app/api/mobile/master/bank/route.ts | 2 - src/app/api/mobile/notification/[id]/route.ts | 62 ++++++++++++------ .../notification/[id]/unread-count/route.ts | 40 ++++++++++-- src/app/api/mobile/profile/[id]/route.ts | 2 - src/app/api/mobile/route.ts | 2 - src/app/api/new/home/route.ts | 2 - .../api/new/investasi/berita/[id]/route.ts | 3 - .../api/new/investasi/dokumen/[id]/route.ts | 2 - src/app/api/profile/[id]/route.ts | 2 - src/app/api/profile/route.ts | 2 - src/app/api/user-validate/route.ts | 53 +++++++++++---- src/app/api/user/route.ts | 2 - .../_global/fun/generate_seeder.ts | 2 - .../admin/donasi/fun/get/get_one_by_id.ts | 1 - .../donasi/fun/update/fun_status_publish.ts | 2 - .../create/fun_create_notif_to_all_user.ts | 2 - .../fun/create/fun_create_notif_user.ts | 2 - .../donasi/fun/get/fun_check_status.tsx | 3 - src/lib/prisma.ts | 50 ++++++++++++--- 33 files changed, 312 insertions(+), 128 deletions(-) diff --git a/.env.example b/.env.example index ec0cd612..8c9b7df7 100644 --- a/.env.example +++ b/.env.example @@ -1,8 +1,11 @@ # ============================== # Database # ============================== -# Tambahkan connection_limit dan pool_timeout untuk mencegah connection exhaustion -DATABASE_URL="postgresql://user:password@localhost:5432/dbname?connection_limit=10&pool_timeout=20" +# Connection pool settings untuk mencegah connection exhaustion: +# - connection_limit=10: Maksimal 10 koneksi per Prisma Client instance +# - pool_timeout=20: Timeout menunggu koneksi tersedia (detik) +# - connect_timeout=10: Timeout untuk membuat koneksi baru (detik) +DATABASE_URL="postgresql://user:password@localhost:5432/dbname?connection_limit=10&pool_timeout=20&connect_timeout=10" # ============================== # Auth / Session diff --git a/QWEN.md b/QWEN.md index bffa3cc7..cd74db3c 100644 --- a/QWEN.md +++ b/QWEN.md @@ -120,14 +120,6 @@ The team follows a structured Git workflow: - `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 @@ -140,6 +132,62 @@ Body: References: #issue-number ``` +**Example:** +``` +feat: Tambahkan fitur kalkulator + +Deskripsi: +- Menambahkan fungsi penambahan, pengurangan, perkalian, dan pembagian +- Memperbolehkan pengguna untuk memasukkan dua angka dan melakukan operasi matematika + +Fixes #12 +``` + +### 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 + +### Comment Standards + +**File Header:** +```typescript +/** + * Nama File: app.ts + * Deskripsi: Ini adalah file utama aplikasi. + * Pembuat: John Doe + * Tanggal: 27 Juli 2023 + */ +``` + +**Function Comments:** +```typescript +/** + * Fungsi untuk menambahkan dua angka. + * @param {number} a - Angka pertama. + * @param {number} b - Angka kedua. + * @returns {number} Hasil penjumlahan a dan b. + */ +function addNumbers(a: number, b: number): number { + return a + b; +} +``` + +**Custom Type Comments:** +```typescript +/** + * Interface untuk merepresentasikan informasi pelanggan. + */ +interface Customer { + id: number; // ID pelanggan + name: string; // Nama pelanggan + age?: number; // Umur pelanggan (opsional) +} +``` + ## Project Structure ### Main Directories diff --git a/prisma/seed.ts b/prisma/seed.ts index 6d08e31a..b2d8326f 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -13,6 +13,6 @@ import { generate_seeder } from "./../src/app_modules/_global/fun/generate_seede console.error("<< error seeder", e); process.exit(1); }) - .finally(async () => { - await prisma.$disconnect(); - }); + // .finally(async () => { + // await prisma.$disconnect(); + // }); diff --git a/src/app/api/admin/notifikasi/count/route.tsx b/src/app/api/admin/notifikasi/count/route.tsx index 1d2d8b5a..ab335dfe 100644 --- a/src/app/api/admin/notifikasi/count/route.tsx +++ b/src/app/api/admin/notifikasi/count/route.tsx @@ -1,4 +1,5 @@ import { NextResponse } from "next/server"; +import { withRetry } from "@/lib/prisma-retry"; import { prisma } from "@/lib"; export const dynamic = "force-dynamic"; @@ -16,13 +17,25 @@ export async function GET(request: Request) { const { searchParams } = new URL(request.url); const userId = searchParams.get("id"); - const data = await prisma.notifikasi.count({ - where: { - adminId: userId, - userRoleId: "2", - isRead: false, - }, - }); + if (!userId) { + return NextResponse.json( + { success: false, message: "User ID is required" }, + { status: 400 } + ); + } + + const data = await withRetry( + () => + prisma.notifikasi.count({ + where: { + adminId: userId, + userRoleId: "2", + isRead: false, + }, + }), + undefined, + "countAdminNotifications" + ); return NextResponse.json( { @@ -33,7 +46,25 @@ export async function GET(request: Request) { { status: 200 } ); } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error"; console.error("Error get count notifikasi", error); + + // Check if it's a database connection error + if ( + errorMsg.includes("Prisma") || + errorMsg.includes("database") || + errorMsg.includes("connection") + ) { + return NextResponse.json( + { + success: false, + message: "Database connection error. Please try again.", + data: null, + }, + { status: 503 } + ); + } + return NextResponse.json( { success: false, diff --git a/src/app/api/auth/code/[id]/route.ts b/src/app/api/auth/code/[id]/route.ts index f16fce90..8e807ec3 100644 --- a/src/app/api/auth/code/[id]/route.ts +++ b/src/app/api/auth/code/[id]/route.ts @@ -50,7 +50,5 @@ async function DELETE( }, { status: 500 } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/auth/mobile-login/route.ts b/src/app/api/auth/mobile-login/route.ts index ea49ad24..6379b272 100644 --- a/src/app/api/auth/mobile-login/route.ts +++ b/src/app/api/auth/mobile-login/route.ts @@ -1,3 +1,4 @@ +import { withRetry } from "@/lib/prisma-retry"; import { prisma } from "@/lib"; import { randomOTP } from "@/app_modules/auth/fun/rondom_otp"; import { NextResponse } from "next/server"; @@ -9,11 +10,26 @@ export async function POST(req: Request) { const body = await req.json(); const { nomor } = body; - const user = await prisma.user.findUnique({ - where: { - nomor: nomor, - }, - }); + if (!nomor) { + return NextResponse.json( + { + success: false, + message: "Nomor telepon diperlukan", + status: 400, + } + ); + } + + const user = await withRetry( + () => + prisma.user.findUnique({ + where: { + nomor: nomor, + }, + }), + undefined, + "findUserByNomor" + ); if (!user) return NextResponse.json({ @@ -22,12 +38,17 @@ export async function POST(req: Request) { status: 404, }); - const createOtpId = await prisma.kodeOtp.create({ - data: { - nomor: nomor, - otp: codeOtp, - }, - }); + const createOtpId = await withRetry( + () => + prisma.kodeOtp.create({ + data: { + nomor: nomor, + otp: codeOtp, + }, + }), + undefined, + "createOTP" + ); if (!createOtpId) return NextResponse.json( @@ -59,6 +80,25 @@ export async function POST(req: Request) { { status: 200 }, ); } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error"; + console.error("Mobile login error:", error); + + // Check if it's a database connection error + if ( + errorMsg.includes("Prisma") || + errorMsg.includes("database") || + errorMsg.includes("connection") + ) { + return NextResponse.json( + { + success: false, + message: "Database connection error. Please try again.", + status: 503, + }, + { status: 503 } + ); + } + return NextResponse.json( { success: false, diff --git a/src/app/api/auth/resend/route.ts b/src/app/api/auth/resend/route.ts index adc6491c..a1c5cb14 100644 --- a/src/app/api/auth/resend/route.ts +++ b/src/app/api/auth/resend/route.ts @@ -64,7 +64,5 @@ export async function POST(req: Request) { }, { status: 500 }, ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/event/[id]/route.ts b/src/app/api/event/[id]/route.ts index 81849514..00615d80 100644 --- a/src/app/api/event/[id]/route.ts +++ b/src/app/api/event/[id]/route.ts @@ -42,7 +42,5 @@ export async function GET( { success: false, message: "Gagal mendapatkan data" }, { status: 500 } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/event/check-peserta/route.ts b/src/app/api/event/check-peserta/route.ts index 4333609f..6c6cf3fe 100644 --- a/src/app/api/event/check-peserta/route.ts +++ b/src/app/api/event/check-peserta/route.ts @@ -30,13 +30,11 @@ export async function GET(request: Request) { fixData = false; } - await prisma.$disconnect(); return NextResponse.json( { success: true, message: "Success get data", data: fixData }, { status: 200 } ); } catch (error) { - await prisma.$disconnect(); backendLogger.error("Error get data detail event:", error); return NextResponse.json( { diff --git a/src/app/api/event/sponsor/[id]/route.ts b/src/app/api/event/sponsor/[id]/route.ts index 9860ed0d..b997092c 100644 --- a/src/app/api/event/sponsor/[id]/route.ts +++ b/src/app/api/event/sponsor/[id]/route.ts @@ -41,13 +41,11 @@ export async function POST( }, }); - await prisma.$disconnect(); return NextResponse.json({ success: true, message: "Success create sponsor", }); } catch (error) { - await prisma.$disconnect(); backendLogger.error("Error create sponsor event", error); return NextResponse.json( { success: false, message: "Failed create sponsor" }, @@ -100,7 +98,5 @@ export async function GET( }, { status: 500 } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/event/sponsor/list/[id]/route.ts b/src/app/api/event/sponsor/list/[id]/route.ts index cc1f8ac9..bddbd058 100644 --- a/src/app/api/event/sponsor/list/[id]/route.ts +++ b/src/app/api/event/sponsor/list/[id]/route.ts @@ -58,7 +58,6 @@ export async function GET( }); } - await prisma.$disconnect(); return NextResponse.json({ success: true, message: "Success create sponsor", @@ -66,7 +65,6 @@ export async function GET( }); } catch (error) { backendLogger.error("Error get sponsor event", error); - await prisma.$disconnect(); return NextResponse.json( { success: false, diff --git a/src/app/api/event/tipe-acara/route.ts b/src/app/api/event/tipe-acara/route.ts index 93da9808..63cc105a 100644 --- a/src/app/api/event/tipe-acara/route.ts +++ b/src/app/api/event/tipe-acara/route.ts @@ -33,7 +33,5 @@ export async function GET(request: Request) { }, { status: 500 } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/master/bank/route.ts b/src/app/api/master/bank/route.ts index f681d0f4..b3497afc 100644 --- a/src/app/api/master/bank/route.ts +++ b/src/app/api/master/bank/route.ts @@ -35,7 +35,5 @@ export async function GET(request: Request) { }, { status: 500 } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/master/status_transaksi/route.ts b/src/app/api/master/status_transaksi/route.ts index 06840205..a0cfed96 100644 --- a/src/app/api/master/status_transaksi/route.ts +++ b/src/app/api/master/status_transaksi/route.ts @@ -21,13 +21,11 @@ export async function GET(request: Request) { }, }); - await prisma.$disconnect(); return NextResponse.json( { success: true, message: "Berhasil mendapatkan data", data: res }, { status: 200 } ); } catch (error) { - await prisma.$disconnect(); backendLogger.error("Error Get Master Status Transaksi >>", error); return NextResponse.json( { diff --git a/src/app/api/mobile/master/bank/route.ts b/src/app/api/mobile/master/bank/route.ts index 7ecd46d5..a9b2488a 100644 --- a/src/app/api/mobile/master/bank/route.ts +++ b/src/app/api/mobile/master/bank/route.ts @@ -28,7 +28,5 @@ async function GET() { }, { status: 500 } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/mobile/notification/[id]/route.ts b/src/app/api/mobile/notification/[id]/route.ts index e9af6ec6..3696a905 100644 --- a/src/app/api/mobile/notification/[id]/route.ts +++ b/src/app/api/mobile/notification/[id]/route.ts @@ -1,3 +1,4 @@ +import { withRetry } from "@/lib/prisma-retry"; import { prisma } from "@/lib"; import _ from "lodash"; import { NextRequest, NextResponse } from "next/server"; @@ -22,28 +23,38 @@ export async function GET( let fixData; try { - const data = await prisma.notifikasi.findMany({ - take: page ? takeData : undefined, - skip: page ? skipData : undefined, - orderBy: { - createdAt: "desc", - }, - where: { - recipientId: id, - kategoriApp: fixCategory, - }, - }); + const data = await withRetry( + () => + prisma.notifikasi.findMany({ + take: page ? takeData : undefined, + skip: page ? skipData : undefined, + orderBy: { + createdAt: "desc", + }, + where: { + recipientId: id, + kategoriApp: fixCategory, + }, + }), + undefined, + "getNotifications" + ); // 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, - }, - }); + totalCount = await withRetry( + () => + prisma.notifikasi.count({ + where: { + recipientId: id, + kategoriApp: fixCategory, + }, + }), + undefined, + "countNotifications" + ); totalPages = Math.ceil(totalCount / takeData); } @@ -69,8 +80,23 @@ export async function GET( return NextResponse.json(response); } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error"; + console.error("Error getting notifications:", error); + + // Check if it's a database connection error + if ( + errorMsg.includes("Prisma") || + errorMsg.includes("database") || + errorMsg.includes("connection") + ) { + return NextResponse.json( + { error: "Database connection error. Please try again." }, + { status: 503 } + ); + } + return NextResponse.json( - { error: (error as Error).message }, + { error: errorMsg }, { status: 500 }, ); } diff --git a/src/app/api/mobile/notification/[id]/unread-count/route.ts b/src/app/api/mobile/notification/[id]/unread-count/route.ts index c2379eff..0b4a01fd 100644 --- a/src/app/api/mobile/notification/[id]/unread-count/route.ts +++ b/src/app/api/mobile/notification/[id]/unread-count/route.ts @@ -1,3 +1,4 @@ +import { withRetry } from "@/lib/prisma-retry"; import { prisma } from "@/lib"; import { NextRequest, NextResponse } from "next/server"; @@ -9,12 +10,24 @@ export async function GET( console.log("User ID:", id); try { - const data = await prisma.notifikasi.count({ - where: { - recipientId: id, - isRead: false, - }, - }); + if (!id) { + return NextResponse.json({ + success: false, + message: "User ID is required", + }); + } + + const data = await withRetry( + () => + prisma.notifikasi.count({ + where: { + recipientId: id, + isRead: false, + }, + }), + undefined, + "countUnreadNotifications" + ); console.log("List Notification >>", data); @@ -23,6 +36,21 @@ export async function GET( data: data, }); } catch (error) { + const errorMsg = error instanceof Error ? error.message : "Unknown error"; + console.error("Error getting unread count:", error); + + // Check if it's a database connection error + if ( + errorMsg.includes("Prisma") || + errorMsg.includes("database") || + errorMsg.includes("connection") + ) { + return NextResponse.json({ + success: false, + message: "Database connection error. Please try again.", + }); + } + return NextResponse.json({ success: false, message: "Failed to get unread count", diff --git a/src/app/api/mobile/profile/[id]/route.ts b/src/app/api/mobile/profile/[id]/route.ts index fd6239ee..f2b48d9e 100644 --- a/src/app/api/mobile/profile/[id]/route.ts +++ b/src/app/api/mobile/profile/[id]/route.ts @@ -131,7 +131,5 @@ async function PUT(request: Request, { params }: { params: { id: string } }) { }, { status: 500 } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/mobile/route.ts b/src/app/api/mobile/route.ts index 79fc8bcd..a72814ab 100644 --- a/src/app/api/mobile/route.ts +++ b/src/app/api/mobile/route.ts @@ -40,7 +40,5 @@ export async function GET(request: Request) { status: 500, } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/new/home/route.ts b/src/app/api/new/home/route.ts index 640d45ef..27925395 100644 --- a/src/app/api/new/home/route.ts +++ b/src/app/api/new/home/route.ts @@ -78,7 +78,5 @@ export async function GET(request: Request) { }, { status: 500 } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/new/investasi/berita/[id]/route.ts b/src/app/api/new/investasi/berita/[id]/route.ts index 7c0f27ed..b67c8f9e 100644 --- a/src/app/api/new/investasi/berita/[id]/route.ts +++ b/src/app/api/new/investasi/berita/[id]/route.ts @@ -49,14 +49,11 @@ export async function GET( }); } - await prisma.$disconnect(); - return NextResponse.json( { success: true, message: "Success get data news", data: fixData }, { status: 200 } ); } catch (error) { - await prisma.$disconnect(); backendLogger.error("Error get data news", error); return NextResponse.json( { diff --git a/src/app/api/new/investasi/dokumen/[id]/route.ts b/src/app/api/new/investasi/dokumen/[id]/route.ts index b4146075..fb1cfe94 100644 --- a/src/app/api/new/investasi/dokumen/[id]/route.ts +++ b/src/app/api/new/investasi/dokumen/[id]/route.ts @@ -36,8 +36,6 @@ export async function GET( }); } - await prisma.$disconnect(); - return NextResponse.json( { success: true, message: "Success get data document", data: fixData }, { status: 200 } diff --git a/src/app/api/profile/[id]/route.ts b/src/app/api/profile/[id]/route.ts index edad427e..00d2d59c 100644 --- a/src/app/api/profile/[id]/route.ts +++ b/src/app/api/profile/[id]/route.ts @@ -104,7 +104,5 @@ async function PUT(request: Request) { }, { status: 500 } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/profile/route.ts b/src/app/api/profile/route.ts index 5a1c8bfe..a9c668ba 100644 --- a/src/app/api/profile/route.ts +++ b/src/app/api/profile/route.ts @@ -77,7 +77,5 @@ async function POST(request: Request) { }, { status: 500 } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app/api/user-validate/route.ts b/src/app/api/user-validate/route.ts index 36e5aa25..b5ff64b6 100644 --- a/src/app/api/user-validate/route.ts +++ b/src/app/api/user-validate/route.ts @@ -1,4 +1,5 @@ import { decrypt } from "@/app/(auth)/_lib/decrypt"; +import { withRetry } from "@/lib/prisma-retry"; import { prisma } from "@/lib"; import { cookies } from "next/headers"; import { NextResponse } from "next/server"; @@ -43,11 +44,16 @@ export async function GET(req: Request) { ); } - const user = await prisma.user.findUnique({ - where: { - id: decrypted.id, - }, - }); + const user = await withRetry( + () => + prisma.user.findUnique({ + where: { + id: decrypted.id, + }, + }), + undefined, + "validateUser" + ); if (!user) { return NextResponse.json( @@ -76,25 +82,44 @@ export async function GET(req: Request) { data: user, }); } catch (error) { - const errorMsg = error instanceof Error ? error.message : 'Unknown error'; - const errorStack = error instanceof Error ? error.stack : 'No stack'; - + const errorMsg = error instanceof Error ? error.message : "Unknown error"; + const errorStack = error instanceof Error ? error.stack : "No stack"; + // Log detailed error for debugging console.error("❌ [USER-VALIDATE] Error:", errorMsg); console.error("❌ [USER-VALIDATE] Stack:", errorStack); console.error("❌ [USER-VALIDATE] Time:", new Date().toISOString()); - + // Check if it's a database connection error - if (errorMsg.includes("Prisma") || errorMsg.includes("database") || errorMsg.includes("connection")) { - console.error("❌ [USER-VALIDATE] Database connection error detected!"); - console.error("❌ [USER-VALIDATE] DATABASE_URL exists:", !!process.env.DATABASE_URL); + if ( + errorMsg.includes("Prisma") || + errorMsg.includes("database") || + errorMsg.includes("connection") + ) { + console.error( + "❌ [USER-VALIDATE] Database connection error detected!" + ); + console.error( + "❌ [USER-VALIDATE] DATABASE_URL exists:", + !!process.env.DATABASE_URL + ); + + return NextResponse.json( + { + success: false, + message: "Database connection error. Please try again.", + error: process.env.NODE_ENV === "development" ? errorMsg : "Internal server error", + }, + { status: 503 } + ); } - + return NextResponse.json( { success: false, message: "Terjadi kesalahan pada server", - error: process.env.NODE_ENV === 'development' ? errorMsg : 'Internal server error', + error: + process.env.NODE_ENV === "development" ? errorMsg : "Internal server error", }, { status: 500 } ); diff --git a/src/app/api/user/route.ts b/src/app/api/user/route.ts index 6eec0018..a7a85fb7 100644 --- a/src/app/api/user/route.ts +++ b/src/app/api/user/route.ts @@ -125,7 +125,5 @@ export async function GET(request: Request) { status: 500, } ); - } finally { - await prisma.$disconnect(); } } diff --git a/src/app_modules/_global/fun/generate_seeder.ts b/src/app_modules/_global/fun/generate_seeder.ts index 95805602..9d725a39 100644 --- a/src/app_modules/_global/fun/generate_seeder.ts +++ b/src/app_modules/_global/fun/generate_seeder.ts @@ -669,10 +669,8 @@ const limit = pLimit(1); export async function generate_seeder() { try { await Promise.all(listSeederQueue.map((fn) => limit(fn))); - await prisma.$disconnect(); } catch (error) { console.error("error generate seeder", error); - await prisma.$disconnect(); } return { status: 200, success: true }; diff --git a/src/app_modules/admin/donasi/fun/get/get_one_by_id.ts b/src/app_modules/admin/donasi/fun/get/get_one_by_id.ts index d482d363..3667d10c 100644 --- a/src/app_modules/admin/donasi/fun/get/get_one_by_id.ts +++ b/src/app_modules/admin/donasi/fun/get/get_one_by_id.ts @@ -37,6 +37,5 @@ export async function AdminDonasi_getOneById(id: string) { }, }); - await prisma.$disconnect(); return res; } diff --git a/src/app_modules/admin/donasi/fun/update/fun_status_publish.ts b/src/app_modules/admin/donasi/fun/update/fun_status_publish.ts index 4a3bfa30..981ea92a 100644 --- a/src/app_modules/admin/donasi/fun/update/fun_status_publish.ts +++ b/src/app_modules/admin/donasi/fun/update/fun_status_publish.ts @@ -33,12 +33,10 @@ export async function AdminDonasi_funUpdateStatusPublish( }); if (!data) { - await prisma.$disconnect(); return { status: 400, message: "Data tidak ditemukan" }; } revalidatePath(RouterAdminDonasi.table_review); - await prisma.$disconnect(); return { data: data, diff --git a/src/app_modules/admin/notifikasi/fun/create/fun_create_notif_to_all_user.ts b/src/app_modules/admin/notifikasi/fun/create/fun_create_notif_to_all_user.ts index 42c2de73..daf7d1dc 100644 --- a/src/app_modules/admin/notifikasi/fun/create/fun_create_notif_to_all_user.ts +++ b/src/app_modules/admin/notifikasi/fun/create/fun_create_notif_to_all_user.ts @@ -36,12 +36,10 @@ export default async function adminNotifikasi_funCreateToAllUser({ }, }); if (!create) { - await prisma.$disconnect(); return { status: 400, message: "Gagal mengirim notifikasi" }; } } - await prisma.$disconnect(); return { status: 201, message: "Berhasil mengirim notifikasi", diff --git a/src/app_modules/admin/notifikasi/fun/create/fun_create_notif_user.ts b/src/app_modules/admin/notifikasi/fun/create/fun_create_notif_user.ts index 28b4c2a7..c077ffec 100644 --- a/src/app_modules/admin/notifikasi/fun/create/fun_create_notif_user.ts +++ b/src/app_modules/admin/notifikasi/fun/create/fun_create_notif_user.ts @@ -25,10 +25,8 @@ export default async function adminNotifikasi_funCreateToUser({ }); if (!create) { - await prisma.$disconnect(); return { status: 400, message: "Gagal mengirim notifikasi" }; } - await prisma.$disconnect(); return { status: 201, message: "Berhasil mengirim notifikasi" }; } diff --git a/src/app_modules/donasi/fun/get/fun_check_status.tsx b/src/app_modules/donasi/fun/get/fun_check_status.tsx index e824768b..699218a2 100644 --- a/src/app_modules/donasi/fun/get/fun_check_status.tsx +++ b/src/app_modules/donasi/fun/get/fun_check_status.tsx @@ -9,9 +9,6 @@ export async function donasi_checkStatus({ id }: { id: string }) { }, }); - await prisma.$disconnect(); - - if (checkStatus?.donasiMaster_StatusDonasiId == "2") return true; return false; diff --git a/src/lib/prisma.ts b/src/lib/prisma.ts index a21f3fc8..5cfc6aa5 100644 --- a/src/lib/prisma.ts +++ b/src/lib/prisma.ts @@ -1,21 +1,55 @@ import { PrismaClient } from "@prisma/client"; +/** + * Instance global Prisma client untuk connection pooling + * Menggunakan pattern globalThis untuk mencegah multiple instance selama: + * - Hot module replacement (HMR) di development + * - Multiple import di seluruh aplikasi + * - Server-side rendering di Next.js + */ declare global { + // eslint-disable-next-line no-var var prisma: PrismaClient | undefined; } -// if (!process.env.DATABASE_URL) { -// throw new Error("DATABASE_URL is required but not found in environment variables"); -// } - +// Konfigurasi connection pool via parameter query DATABASE_URL: +// connection_limit=10&pool_timeout=20&connect_timeout=10 const prisma = - global.prisma ?? + globalThis.prisma ?? new PrismaClient({ - log: process.env.NODE_ENV === "development" ? ["error", "warn", "query"] : ["error", "warn"], + log: + process.env.NODE_ENV === "development" + ? ["error", "warn"] + : ["error"], + datasources: { + db: { + url: process.env.DATABASE_URL, + }, + }, }); -// Selalu assign ke global agar hanya ada 1 instance (dev: cegah hot-reload, prod: cegah multiple instances) -global.prisma = prisma; +// Hanya assign ke global di development untuk mencegah multiple instance saat HMR +// Di production, ini di-skip karena tidak ada HMR +if (process.env.NODE_ENV !== "production") { + globalThis.prisma = prisma; +} + +/** + * Handler graceful shutdown untuk koneksi Prisma + * Panggil ini HANYA saat terminasi process (SIGINT/SIGTERM) + * JANGAN panggil $disconnect() setelah query individual + */ +async function gracefulShutdown(): Promise { + console.log("[Prisma] Menutup koneksi database..."); + await prisma.$disconnect(); + console.log("[Prisma] Semua koneksi ditutup"); +} + +// Register shutdown handlers (hanya di environment Node.js) +if (typeof process !== "undefined") { + process.on("SIGINT", gracefulShutdown); + process.on("SIGTERM", gracefulShutdown); +} export default prisma; export { prisma }; -- 2.49.1 From cbfd105134fa848689dabda542aaa7cfb9f51b99 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Thu, 5 Mar 2026 14:30:03 +0800 Subject: [PATCH 2/3] chore(release): 1.6.8 --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 83391410..90c325da 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.6.8](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.6.7...v1.6.8) (2026-03-05) + ## [1.6.7](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.6.6...v1.6.7) (2026-03-04) ## [1.6.6](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.6.5...v1.6.6) (2026-03-03) diff --git a/package.json b/package.json index 52fd6f55..df2b869b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hipmi", - "version": "1.6.7", + "version": "1.6.8", "private": true, "prisma": { "seed": "bun prisma/seed.ts" -- 2.49.1 From b2305a35a66184cea564f15d0c9532f24740d74b Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Thu, 5 Mar 2026 16:38:31 +0800 Subject: [PATCH 3/3] Fix WA Otp ### NO Issue --- src/lib/code-otp-sender.ts | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/lib/code-otp-sender.ts b/src/lib/code-otp-sender.ts index 30376c21..c7213348 100644 --- a/src/lib/code-otp-sender.ts +++ b/src/lib/code-otp-sender.ts @@ -7,23 +7,24 @@ const sendCodeOtp = async ({ codeOtp?: string; newMessage?: string; }) => { - 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=${enCode}`, - { - cache: "no-cache", - headers: { - Authorization: `Bearer ${process.env.WA_SERVER_TOKEN}`, - }, + 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 = msg; + + const res = await fetch(`https://otp.wibudev.com/api/wa/send-text`, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${process.env.WA_SERVER_TOKEN}`, }, - ); - // const res = await fetch( - // `https://wa.wibudev.com/code?nom=${nomor}&text=HIPMI - Kode ini bersifat RAHASIA dan JANGAN DI BAGIKAN KEPADA SIAPAPUN, termasuk anggota ataupun pengurus HIPMI lainnya. - // \n - // >> Kode OTP anda: ${codeOtp}. - // `, - // ); + body: JSON.stringify({ + number: nomor, + text: enCode, + }), + }); + + console.log("RES >>", res); return res; }; -- 2.49.1