diff --git a/CHANGELOG.md b/CHANGELOG.md index 566c8fac..07393839 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.5.31](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.30...v1.5.31) (2025-12-24) + ## [1.5.30](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.29...v1.5.30) (2025-12-19) ## [1.5.29](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.28...v1.5.29) (2025-12-17) diff --git a/package.json b/package.json index 35b74a7c..997829db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hipmi", - "version": "1.5.30", + "version": "1.5.31", "private": true, "prisma": { "seed": "bun prisma/seed.ts" diff --git a/prisma/migrations/20251223084450_add_recipient_and_sender/migration.sql b/prisma/migrations/20251223084450_add_recipient_and_sender/migration.sql new file mode 100644 index 00000000..8c447d37 --- /dev/null +++ b/prisma/migrations/20251223084450_add_recipient_and_sender/migration.sql @@ -0,0 +1,16 @@ +-- DropForeignKey +ALTER TABLE "Notifikasi" DROP CONSTRAINT "Notifikasi_userRoleId_fkey"; + +-- AlterTable +ALTER TABLE "Notifikasi" ADD COLUMN "recipientId" TEXT, +ADD COLUMN "senderId" TEXT, +ALTER COLUMN "userRoleId" DROP NOT NULL; + +-- AddForeignKey +ALTER TABLE "Notifikasi" ADD CONSTRAINT "Notifikasi_userRoleId_fkey" FOREIGN KEY ("userRoleId") REFERENCES "MasterUserRole"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Notifikasi" ADD CONSTRAINT "Notifikasi_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Notifikasi" ADD CONSTRAINT "Notifikasi_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 4d6ef14a..831d6714 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -59,6 +59,10 @@ model User { acceptedTermsAt DateTime? acceptedForumTermsAt DateTime? tokenUserDevices TokenUserDevice[] + + // For Mobile App + NotificationRecipient Notifikasi[] @relation("NotificationRecipient") + NotificationSender Notifikasi[] @relation("NotificationSender") } model MasterUserRole { @@ -587,7 +591,7 @@ model Donasi_Invoice { imageId String? MasterBank MasterBank? @relation(fields: [masterBankId], references: [id]) - masterBankId String? + masterBankId String? } model Donasi_Kabar { @@ -974,28 +978,36 @@ model NomorAdmin { } model Notifikasi { - id String @id @default(cuid()) - isActive Boolean @default(true) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt appId String kategoriApp String pesan String title String? status String? - - isRead Boolean @default(false) - readAt DateTime? // kapan user membaca notifikasi ini - deepLink String? // misal: "announcement/123", "user/profile/cmha6wb9w0001cfndwl9fcse6" - type String? + + isRead Boolean @default(false) + readAt DateTime? // kapan user membaca notifikasi ini + deepLink String? // misal: "announcement/123", "user/profile/cmha6wb9w0001cfndwl9fcse6" + type String? Role MasterUserRole? @relation(fields: [userRoleId], references: [id]) - userRoleId String + userRoleId String? User User? @relation("UserNotifikasi", fields: [userId], references: [id], map: "NotifikasiUser") userId String? Admin User? @relation("AdminNotifikasi", fields: [adminId], references: [id], map: "NotifikasiAdmin") adminId String? + + // Recipient (user who receives the notification) + recipient User? @relation("NotificationRecipient", fields: [recipientId], references: [id]) + recipientId String? + + // Sender (user who sent the notification) + sender User? @relation("NotificationSender", fields: [senderId], references: [id]) + senderId String? } // MAPS diff --git a/src/app/api/mobile/auth/device-tokens/[id]/route.ts b/src/app/api/mobile/auth/device-tokens/[id]/route.ts index 72ce5ed5..36b7ceb0 100644 --- a/src/app/api/mobile/auth/device-tokens/[id]/route.ts +++ b/src/app/api/mobile/auth/device-tokens/[id]/route.ts @@ -8,10 +8,17 @@ async function DELETE( { params }: { params: { id: string } } ) { const { id } = params; + const { searchParams } = new URL(request.url); + const deviceId = searchParams.get("deviceId"); + + console.log("ID", id); + console.log("DEVICE ID", deviceId); + try { const findFirst = await prisma.tokenUserDevice.findFirst({ where: { userId: id, + deviceId: deviceId as any, }, }); diff --git a/src/app/api/mobile/auth/device-tokens/route.ts b/src/app/api/mobile/auth/device-tokens/route.ts index 70a388f2..2b307500 100644 --- a/src/app/api/mobile/auth/device-tokens/route.ts +++ b/src/app/api/mobile/auth/device-tokens/route.ts @@ -6,7 +6,6 @@ export { POST, GET }; async function POST(request: NextRequest) { const { data } = await request.json(); try { - console.log("Data >>", JSON.stringify(data, null, 2)); const { userId, platform, deviceId, model, appVersion, fcmToken } = data; diff --git a/src/app/api/mobile/notification/[id]/route.ts b/src/app/api/mobile/notification/[id]/route.ts index fb446628..8bed585b 100644 --- a/src/app/api/mobile/notification/[id]/route.ts +++ b/src/app/api/mobile/notification/[id]/route.ts @@ -1,4 +1,5 @@ import { prisma } from "@/lib"; +import _ from "lodash"; import { NextRequest, NextResponse } from "next/server"; export async function GET( @@ -8,33 +9,22 @@ export async function GET( const { id } = params; const { searchParams } = new URL(request.url); const category = searchParams.get("category"); + + let fixData; + const fixCategory = _.upperCase(category || ""); try { - let fixData; + const data = await prisma.notifikasi.findMany({ + orderBy: { + createdAt: "desc", + }, + where: { + recipientId: id, + kategoriApp: fixCategory, + }, + }); - if (category === "count-as-unread") { - const data = await prisma.notifikasi.findMany({ - where: { - userId: id, - isRead: false, - }, - }); - - fixData = data.length; - } else if (category === "all") { - const data = await prisma.notifikasi.findMany({ - where: { - userId: id, - }, - }); - - fixData = data; - } else { - return NextResponse.json({ - success: false, - message: "Invalid category", - }); - } + fixData = data; return NextResponse.json({ success: true, @@ -47,3 +37,31 @@ export async function GET( ); } } + +export async function PUT( + request: NextRequest, + { params }: { params: { id: string } } +) { + const { id } = params; + + try { + await prisma.notifikasi.update({ + where: { + id: id, + }, + data: { + isRead: true, + }, + }); + + return NextResponse.json({ + success: true, + message: "Notifications marked as read", + }); + } catch (error) { + return NextResponse.json( + { error: (error as Error).message }, + { 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 1d32140c..c2379eff 100644 --- a/src/app/api/mobile/notification/[id]/unread-count/route.ts +++ b/src/app/api/mobile/notification/[id]/unread-count/route.ts @@ -6,22 +6,21 @@ export async function GET( { params }: { params: { id: string } } ) { const { id } = params; - - console.log("Id >>", id); + console.log("User ID:", id); try { - const data = await prisma.notifikasi.findMany({ + const data = await prisma.notifikasi.count({ where: { - userId: id, + recipientId: id, isRead: false, }, }); - console.log("Data >>", data); + console.log("List Notification >>", data); return NextResponse.json({ success: true, - data: data.length, + data: data, }); } catch (error) { return NextResponse.json({ diff --git a/src/app/api/mobile/notification/route.ts b/src/app/api/mobile/notification/route.ts index 92b5681a..c98980c5 100644 --- a/src/app/api/mobile/notification/route.ts +++ b/src/app/api/mobile/notification/route.ts @@ -1,29 +1,37 @@ // app/api/test/notifications/route.ts +import { prisma } from "@/lib"; import { adminMessaging } from "@/lib/firebase-admin"; import { NextRequest, NextResponse } from "next/server"; -import { prisma } from "@/lib"; + +type NotificationProp = { + title: string; + body: string; + userLoginId: string; + appId?: string; + status?: string; + kategoriApp?: string; + type?: string; + deepLink?: string; +}; export async function POST(request: NextRequest) { try { const { data } = await request.json(); + const { - fcmToken, title, body: notificationBody, userLoginId, type, kategoriApp, - } = data; + appId, + status, + deepLink, + } = data as NotificationProp; - console.log("Data Notifikasi >>", data); - - if (!fcmToken || !title) { - return NextResponse.json( - { error: "Missing fcmToken or title" }, - { status: 400 } - ); - } + console.log("Notification Send >>", data); + // Cari user yang login const findUserLogin = await prisma.user.findUnique({ where: { id: userLoginId, @@ -34,79 +42,82 @@ export async function POST(request: NextRequest) { return NextResponse.json({ error: "User not found" }, { status: 404 }); } - const deviceToken = await prisma.tokenUserDevice.findMany({ + // Cari token fcm user yang login + const checkFcmToken = await prisma.tokenUserDevice.findFirst({ where: { - isActive: true, - NOT: { - userId: findUserLogin.id, - }, - }, - include: { - user: true, + userId: findUserLogin.id, }, }); - for (let i of deviceToken) { - const message = { - token: i.token, - notification: { - title, - body: notificationBody || "", - }, - data: { - sentAt: new Date().toISOString(), // ✅ Simpan metadata di data - // contoh: senderId, type, etc. - }, - }; - console.log("[MSG]", message); + if (!checkFcmToken) { + return NextResponse.json( + { error: "FCM Token not found" }, + { status: 404 } + ); + } - if (i.user?.masterUserRoleId === "1") { - const createNotification = await prisma.notifikasi.create({ - data: { - title, - type, - createdAt: message.data.sentAt, - appId: "test-id-app", - userRoleId: findUserLogin.masterUserRoleId, - kategoriApp: "PERCOBAAN", - pesan: notificationBody || "", - userId: i.userId, + // Jika user yang masuk maka notifikasik akan dikirim ke semua admin , begitu sebaliknya ! + const filterByCurrentLoginId = + findUserLogin.masterUserRoleId === "1" ? "2" : "1"; + + // Cari user yang akan menerima notifikasi + const findAllUserBySendTo = await prisma.user.findMany({ + where: { + masterUserRoleId: filterByCurrentLoginId, + NOT: { + id: findUserLogin.id, + }, + }, + }); + + console.log("Find All User By Send To >>", findAllUserBySendTo); + + for (let a of findAllUserBySendTo) { + const responseCreatedNotifications = await createNotification({ + title, + type: type as string, + createdAt: new Date(), + pesan: notificationBody || "", + appId: appId as string, + kategoriApp: kategoriApp as string, + userRoleId: findUserLogin.masterUserRoleId, + status: status, + deepLink: deepLink, + senderId: findUserLogin.id, + recipientId: a.id, + }); + + if (responseCreatedNotifications) { + const deviceToken = await prisma.tokenUserDevice.findMany({ + where: { + userId: a.id, + isActive: true, }, }); - if (createNotification) { + for (let i of deviceToken) { + const message = { + token: i.token, + notification: { + title, + body: notificationBody || "", + }, + data: { + sentAt: new Date().toISOString(), // ✅ Simpan metadata di data + id: responseCreatedNotifications.id, + deepLink: deepLink || "", + // contoh: senderId, type, etc. + }, + }; + const response = await adminMessaging.send(message); console.log("✅ FCM sent:", response); - } else { - return NextResponse.json({ - success: false, - message: "Failed to create notification", - }); - } + } } else { - const createNotification = await prisma.notifikasi.create({ - data: { - title, - type, - createdAt: message.data.sentAt, - appId: "test-id-app", - userRoleId: findUserLogin.masterUserRoleId, - kategoriApp: "PERCOBAAN", - pesan: notificationBody || "", - adminId: i.userId, - }, + return NextResponse.json({ + success: false, + message: "Failed to create notification", }); - - if (createNotification) { - const response = await adminMessaging.send(message); - console.log("✅ FCM sent:", response); - } else { - return NextResponse.json({ - success: false, - message: "Failed to create notification", - }); - } - } } @@ -122,3 +133,48 @@ export async function POST(request: NextRequest) { ); } } + +async function createNotification({ + title, + type, + createdAt, + appId, + kategoriApp, + pesan, + userRoleId, + status, + deepLink, + senderId, + recipientId, +}: { + title: string; + type: string; + createdAt: Date; + appId: string; + kategoriApp: string; + userRoleId: string; + status?: string; + deepLink?: string; + pesan: string; + + senderId: string; + recipientId: string; +}) { + const createNotification = await prisma.notifikasi.create({ + data: { + title, + type, + createdAt, + appId, + kategoriApp, + pesan, + userRoleId, + status, + deepLink, + senderId, + recipientId, + }, + }); + + return createNotification; +}