From ac634100b5c97cef14416f96c74376e5409bf67f Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Tue, 6 Jan 2026 12:20:12 +0800 Subject: [PATCH] Notifikasi ke admin untuk user baru mendaftar Fix: - prisma/schema.prisma - src/app/api/auth/mobile-register/route.ts - src/app/api/mobile/admin/user/[id]/route.ts - src/app/api/mobile/notification/[id]/route.ts - src/app/api/mobile/notification/route.ts Add: Migrasi untuk db table notifikasi - prisma/migrations/20260105064508_fix_table_notifikasi_optional_data/ ### No Issue --- .../migration.sql | 4 + prisma/schema.prisma | 6 +- src/app/api/auth/mobile-register/route.ts | 88 +++++++++++- src/app/api/mobile/admin/user/[id]/route.ts | 16 ++- src/app/api/mobile/notification/[id]/route.ts | 132 +++++++++++++++++- src/app/api/mobile/notification/route.ts | 6 +- 6 files changed, 236 insertions(+), 16 deletions(-) create mode 100644 prisma/migrations/20260105064508_fix_table_notifikasi_optional_data/migration.sql diff --git a/prisma/migrations/20260105064508_fix_table_notifikasi_optional_data/migration.sql b/prisma/migrations/20260105064508_fix_table_notifikasi_optional_data/migration.sql new file mode 100644 index 00000000..91086ef5 --- /dev/null +++ b/prisma/migrations/20260105064508_fix_table_notifikasi_optional_data/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "Notifikasi" ALTER COLUMN "appId" DROP NOT NULL, +ALTER COLUMN "kategoriApp" DROP NOT NULL, +ALTER COLUMN "pesan" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 831d6714..81c3273d 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -982,9 +982,9 @@ model Notifikasi { isActive Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - appId String - kategoriApp String - pesan String + appId String? + kategoriApp String? + pesan String? title String? status String? diff --git a/src/app/api/auth/mobile-register/route.ts b/src/app/api/auth/mobile-register/route.ts index 8240bf9a..33754e47 100644 --- a/src/app/api/auth/mobile-register/route.ts +++ b/src/app/api/auth/mobile-register/route.ts @@ -1,5 +1,6 @@ import { sessionCreate } from "@/app/(auth)/_lib/session_create"; import { randomOTP } from "@/app_modules/auth/fun/rondom_otp"; +import { adminMessaging } from "@/lib/firebase-admin"; import prisma from "@/lib/prisma"; import { NextResponse } from "next/server"; @@ -51,12 +52,6 @@ export async function POST(req: Request) { { status: 500 } ); - // const token = await sessionCreate({ - // sessionKey: process.env.NEXT_PUBLIC_BASE_SESSION_KEY!, - // encodedKey: process.env.NEXT_PUBLIC_BASE_TOKEN_KEY!, - // user: createUser as any, - // }); - const createOtpId = await prisma.kodeOtp.create({ data: { nomor: data.nomor, @@ -87,11 +82,90 @@ export async function POST(req: Request) { { status: 400 } ); + // =========== START SEND NOTIFICATION =========== // + + const findAllUserBySendTo = await prisma.user.findMany({ + where: { + masterUserRoleId: "2", + }, + }); + + console.log("Users to notify:", findAllUserBySendTo); + + const dataNotification = { + title: "Pendaftaran Baru", + type: "announcement", + kategoriApp: "OTHER", + createdAt: new Date(), + pesan: "User baru telah melakukan registrasi. Ayo cek dan verifikasi!", + deepLink: `/admin/user-access/${createUser.id}`, + senderId: createUser.id, + }; + + for (let a of findAllUserBySendTo) { + const createdNotification = await prisma.notifikasi.create({ + data: { + ...dataNotification, + recipientId: a.id, + }, + }); + + if (createdNotification) { + const deviceToken = await prisma.tokenUserDevice.findMany({ + where: { + userId: a.id, + isActive: true, + }, + }); + + for (let i of deviceToken) { + const message = { + token: i.token, + notification: { + title: dataNotification.title, + body: dataNotification.pesan, + }, + data: { + sentAt: new Date().toISOString(), // ✅ Simpan metadata di data + id: createdNotification.id, + deepLink: dataNotification.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 + } + } + } + } + + // =========== END SEND NOTIFICATION =========== // + return NextResponse.json( { success: true, message: "Registrasi Berhasil", - // token: token, kodeId: createOtpId.id, }, { status: 201 } diff --git a/src/app/api/mobile/admin/user/[id]/route.ts b/src/app/api/mobile/admin/user/[id]/route.ts index c9cec177..5c58248d 100644 --- a/src/app/api/mobile/admin/user/[id]/route.ts +++ b/src/app/api/mobile/admin/user/[id]/route.ts @@ -34,9 +34,15 @@ async function GET(request: Request, { params }: { params: { id: string } }) { async function PUT(request: Request, { params }: { params: { id: string } }) { const { id } = params; const { data } = await request.json(); + const { searchParams } = new URL(request.url); + const category = searchParams.get("category"); + + console.log("Received data:", data); + console.log("User ID:", id); + console.log("Category:", category); try { - if (data.active) { + if (category === "access") { const updateData = await prisma.user.update({ where: { id: id, @@ -47,7 +53,7 @@ async function PUT(request: Request, { params }: { params: { id: string } }) { }); console.log("[Update Active Berhasil]", updateData); - } else if (data.role) { + } else if (category === "role") { const fixName = _.startCase(data.role.replace(/_/g, " ")); const checkRole = await prisma.masterUserRole.findFirst({ @@ -68,6 +74,12 @@ async function PUT(request: Request, { params }: { params: { id: string } }) { }); console.log("[Update Role Berhasil]", updateData); + } else { + return NextResponse.json({ + status: 400, + success: false, + message: "Invalid category", + }); } return NextResponse.json({ diff --git a/src/app/api/mobile/notification/[id]/route.ts b/src/app/api/mobile/notification/[id]/route.ts index 8bed585b..e9a03897 100644 --- a/src/app/api/mobile/notification/[id]/route.ts +++ b/src/app/api/mobile/notification/[id]/route.ts @@ -1,6 +1,8 @@ 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, @@ -9,7 +11,7 @@ export async function GET( const { id } = params; const { searchParams } = new URL(request.url); const category = searchParams.get("category"); - + let fixData; const fixCategory = _.upperCase(category || ""); @@ -51,6 +53,7 @@ export async function PUT( }, data: { isRead: true, + readAt: new Date(), }, }); @@ -65,3 +68,130 @@ export async function PUT( ); } } + +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 } + ); + } +} diff --git a/src/app/api/mobile/notification/route.ts b/src/app/api/mobile/notification/route.ts index 90b42061..132aa21f 100644 --- a/src/app/api/mobile/notification/route.ts +++ b/src/app/api/mobile/notification/route.ts @@ -3,7 +3,7 @@ import { prisma } from "@/lib"; import { adminMessaging } from "@/lib/firebase-admin"; import { NextRequest, NextResponse } from "next/server"; -type NotificationProp = { +export type NotificationProp = { title: string; body: string; userLoginId: string; @@ -134,12 +134,12 @@ export async function POST(request: NextRequest) { try { const response = await adminMessaging.send(message); console.log( - "✅ FCM sent successfully", + "✅ FCM sent to token:", "Response:", response ); } catch (error: any) { - console.error("❌ FCM send failed:", error); + console.error("❌ FCM send failed for token:", i.token, error); // Lanjutkan ke token berikutnya meski satu gagal } }