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
275 lines
6.8 KiB
TypeScript
275 lines
6.8 KiB
TypeScript
import { withRetry } from "@/lib/prisma-retry";
|
|
import { prisma } from "@/lib";
|
|
import _ from "lodash";
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
import { NotificationProp } from "../route";
|
|
import { adminMessaging } from "@/lib/firebase-admin";
|
|
|
|
export async function GET(
|
|
request: NextRequest,
|
|
{ params }: { params: { id: string } },
|
|
) {
|
|
const { id } = params;
|
|
console.log("ID", id);
|
|
const { searchParams } = new URL(request.url);
|
|
const category = searchParams.get("category");
|
|
const fixCategory = _.upperCase(category || "");
|
|
|
|
const page = Number(searchParams.get("page"));
|
|
console.log("page", page);
|
|
const takeData = 10;
|
|
const skipData = page * takeData - takeData;
|
|
|
|
let fixData;
|
|
|
|
try {
|
|
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 withRetry(
|
|
() =>
|
|
prisma.notifikasi.count({
|
|
where: {
|
|
recipientId: id,
|
|
kategoriApp: fixCategory,
|
|
},
|
|
}),
|
|
undefined,
|
|
"countNotifications"
|
|
);
|
|
totalPages = Math.ceil(totalCount / takeData);
|
|
}
|
|
|
|
fixData = data;
|
|
|
|
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) {
|
|
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: errorMsg },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
}
|
|
|
|
export async function PUT(
|
|
request: NextRequest,
|
|
{ params }: { params: { id: string } },
|
|
) {
|
|
const { id } = params;
|
|
const { searchParams } = new URL(request.url);
|
|
const category = searchParams.get("category");
|
|
|
|
try {
|
|
if (category === "one") {
|
|
await prisma.notifikasi.update({
|
|
where: {
|
|
id: id,
|
|
},
|
|
data: {
|
|
isRead: true,
|
|
readAt: new Date(),
|
|
},
|
|
});
|
|
} else if (category === "all") {
|
|
await prisma.notifikasi.updateMany({
|
|
where: {
|
|
recipientId: id,
|
|
},
|
|
data: {
|
|
isRead: true,
|
|
readAt: new Date(),
|
|
},
|
|
});
|
|
}
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: "Notifications marked as read",
|
|
});
|
|
} catch (error) {
|
|
console.error("Error marking notifications as read:", error);
|
|
return NextResponse.json(
|
|
{ error: (error as Error).message },
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
}
|
|
|
|
// export async function POST(
|
|
// request: NextRequest,
|
|
// { params }: { params: { id: string } }
|
|
// ) {
|
|
// const { id } = params;
|
|
|
|
// const { data } = await request.json();
|
|
|
|
// const {
|
|
// title,
|
|
// body: notificationBody,
|
|
// userLoginId,
|
|
// type,
|
|
// kategoriApp,
|
|
// appId,
|
|
// status,
|
|
// deepLink,
|
|
// } = data as NotificationProp;
|
|
|
|
// console.log("Notification Send >>", data);
|
|
|
|
// try {
|
|
// // Cari user yang login
|
|
// const findUserLogin = await prisma.user.findUnique({
|
|
// where: {
|
|
// id: userLoginId,
|
|
// },
|
|
// });
|
|
|
|
// if (!findUserLogin) {
|
|
// return NextResponse.json({ error: "User not found" }, { status: 404 });
|
|
// }
|
|
|
|
// // Cari token fcm user yang login
|
|
// const checkFcmToken = await prisma.tokenUserDevice.findFirst({
|
|
// where: {
|
|
// userId: findUserLogin.id,
|
|
// },
|
|
// });
|
|
|
|
// if (!checkFcmToken) {
|
|
// return NextResponse.json(
|
|
// { error: "FCM Token not found" },
|
|
// { status: 404 }
|
|
// );
|
|
// }
|
|
|
|
// const created = await prisma.notifikasi.create({
|
|
// data: {
|
|
// title,
|
|
// type,
|
|
// createdAt: new Date(),
|
|
// appId,
|
|
// kategoriApp,
|
|
// pesan: notificationBody || "",
|
|
// userRoleId: findUserLogin.masterUserRoleId,
|
|
// status,
|
|
// deepLink,
|
|
// senderId: findUserLogin.id,
|
|
// recipientId: id,
|
|
// },
|
|
// });
|
|
|
|
// if (created) {
|
|
// const deviceToken = await prisma.tokenUserDevice.findMany({
|
|
// where: {
|
|
// userId: id,
|
|
// isActive: true,
|
|
// },
|
|
// });
|
|
|
|
// for (let i of deviceToken) {
|
|
// const message = {
|
|
// token: i.token,
|
|
// notification: {
|
|
// title,
|
|
// body: notificationBody || "",
|
|
// },
|
|
// data: {
|
|
// sentAt: new Date().toISOString(), // ✅ Simpan metadata di data
|
|
// id: created.id,
|
|
// deepLink: deepLink || "",
|
|
// },
|
|
// // Konfigurasi Android untuk prioritas tinggi
|
|
// android: {
|
|
// priority: "high" as const, // Kirim secepatnya, bahkan di doze mode untuk notifikasi penting
|
|
// notification: {
|
|
// channelId: "default", // Sesuaikan dengan channel yang kamu buat di Android
|
|
// },
|
|
|
|
// ttl: 0 as const, // Kirim secepatnya, jangan tunda
|
|
// },
|
|
// // Opsional: tambahkan untuk iOS juga
|
|
// apns: {
|
|
// payload: {
|
|
// aps: {
|
|
// sound: "default" as const,
|
|
// // 'content-available': 1 as const, // jika butuh silent push
|
|
// },
|
|
// },
|
|
// },
|
|
// };
|
|
|
|
// try {
|
|
// const response = await adminMessaging.send(message);
|
|
// console.log("✅ FCM sent successfully", "Response:", response);
|
|
// } catch (error: any) {
|
|
// console.error("❌ FCM send failed:", error);
|
|
// // Lanjutkan ke token berikutnya meski satu gagal
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// return NextResponse.json({
|
|
// success: true,
|
|
// message: "Notification sent successfully",
|
|
// });
|
|
// } catch (error) {
|
|
// console.error("❌ FCM error:", error);
|
|
|
|
// return NextResponse.json(
|
|
// { error: (error as Error).message },
|
|
// { status: 500 }
|
|
// );
|
|
// }
|
|
// }
|