Usulan Commit Message

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
This commit is contained in:
2026-03-05 14:28:45 +08:00
parent a6c9182a01
commit 3e6c94d77f
33 changed files with 312 additions and 128 deletions

View File

@@ -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,

View File

@@ -50,7 +50,5 @@ async function DELETE(
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -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,

View File

@@ -64,7 +64,5 @@ export async function POST(req: Request) {
},
{ status: 500 },
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -42,7 +42,5 @@ export async function GET(
{ success: false, message: "Gagal mendapatkan data" },
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -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(
{

View File

@@ -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();
}
}

View File

@@ -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,

View File

@@ -33,7 +33,5 @@ export async function GET(request: Request) {
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -35,7 +35,5 @@ export async function GET(request: Request) {
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -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(
{

View File

@@ -28,7 +28,5 @@ async function GET() {
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -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 },
);
}

View File

@@ -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",

View File

@@ -131,7 +131,5 @@ async function PUT(request: Request, { params }: { params: { id: string } }) {
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -40,7 +40,5 @@ export async function GET(request: Request) {
status: 500,
}
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -78,7 +78,5 @@ export async function GET(request: Request) {
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -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(
{

View File

@@ -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 }

View File

@@ -104,7 +104,5 @@ async function PUT(request: Request) {
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -77,7 +77,5 @@ async function POST(request: Request) {
},
{ status: 500 }
);
} finally {
await prisma.$disconnect();
}
}

View File

@@ -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 }
);

View File

@@ -125,7 +125,5 @@ export async function GET(request: Request) {
status: 500,
}
);
} finally {
await prisma.$disconnect();
}
}