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/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/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/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" 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/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; }; 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 };