From 02b25ffc84a940d1499104065a4a33535ac8a496 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Wed, 17 Dec 2025 17:40:56 +0800 Subject: [PATCH 1/4] Penerapaan ke database untuk token device Add: src/app/api/mobile/auth/device-tokens/[id]/ Fix: modified: src/app/api/mobile/auth/device-tokens/route.ts modified: src/app/api/mobile/notifications/route.ts ### No Issue --- .../mobile/auth/device-tokens/[id]/route.ts | 44 +++++++++++++++++++ .../api/mobile/auth/device-tokens/route.ts | 26 +++++++++-- src/app/api/mobile/notifications/route.ts | 43 +++++++++++------- 3 files changed, 95 insertions(+), 18 deletions(-) create mode 100644 src/app/api/mobile/auth/device-tokens/[id]/route.ts diff --git a/src/app/api/mobile/auth/device-tokens/[id]/route.ts b/src/app/api/mobile/auth/device-tokens/[id]/route.ts new file mode 100644 index 00000000..72ce5ed5 --- /dev/null +++ b/src/app/api/mobile/auth/device-tokens/[id]/route.ts @@ -0,0 +1,44 @@ +import { NextRequest, NextResponse } from "next/server"; +import { prisma } from "@/lib"; + +export { DELETE }; + +async function DELETE( + request: NextRequest, + { params }: { params: { id: string } } +) { + const { id } = params; + try { + const findFirst = await prisma.tokenUserDevice.findFirst({ + where: { + userId: id, + }, + }); + + if (!findFirst) { + return NextResponse.json({ + success: false, + message: "User tidak ditemukan !", + }); + } + + const deleted = await prisma.tokenUserDevice.delete({ + where: { + id: findFirst.id, + }, + }); + + console.log("DEL", deleted); + + return NextResponse.json({ + success: true, + message: "Berhasil menghapus device token user", + }); + } catch (error) { + console.log("ERROR", error); + return NextResponse.json( + { error: (error as Error).message, message: "Terjadi error pada API" }, + { status: 500 } + ); + } +} diff --git a/src/app/api/mobile/auth/device-tokens/route.ts b/src/app/api/mobile/auth/device-tokens/route.ts index 010aef4b..9d743e80 100644 --- a/src/app/api/mobile/auth/device-tokens/route.ts +++ b/src/app/api/mobile/auth/device-tokens/route.ts @@ -1,13 +1,14 @@ import { NextRequest, NextResponse } from "next/server"; import { prisma } from "@/lib"; -export async function POST(request: NextRequest) { +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; + const { userId, platform, deviceId, model, appVersion, fcmToken } = data; if (!fcmToken) { return NextResponse.json({ error: "Missing Token" }, { status: 400 }); @@ -62,3 +63,22 @@ export async function POST(request: NextRequest) { ); } } + +async function GET(request: NextRequest) { + try { + const data = await prisma.tokenUserDevice.findMany({ + where: { + isActive: true, + }, + }); + + return NextResponse.json({ success: true, data }); + + + } catch (error) { + return NextResponse.json( + { error: (error as Error).message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/mobile/notifications/route.ts b/src/app/api/mobile/notifications/route.ts index 08cd879e..372d65e4 100644 --- a/src/app/api/mobile/notifications/route.ts +++ b/src/app/api/mobile/notifications/route.ts @@ -5,7 +5,7 @@ import { NextRequest, NextResponse } from "next/server"; export async function POST(request: NextRequest) { try { const { data } = await request.json(); - const { fcmToken, title, body: notificationBody } = data; + const { fcmToken, title, body: notificationBody, userLoginId } = data; console.log("Data Notifikasi >>", data); @@ -16,24 +16,37 @@ export async function POST(request: NextRequest) { ); } - const message = { - token: fcmToken, - notification: { - title, - body: notificationBody || "", + const deviceToken = await prisma.tokenUserDevice.findMany({ + where: { + isActive: true, + NOT: { + userId: userLoginId, + }, }, - data: { - sentAt: new Date().toISOString(), // ✅ Simpan metadata di data - // contoh: senderId, type, etc. - }, - }; + }); - console.log("[MSG]", message); + 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); - const response = await adminMessaging.send(message); - console.log("✅ FCM sent:", response); + const response = await adminMessaging.send(message); + console.log("✅ FCM sent:", response); + } - return NextResponse.json({ success: true, messageId: response }); + return NextResponse.json({ + success: true, + message: "Notification sent successfully", + }); } catch (error: any) { console.error("❌ FCM error:", error); return NextResponse.json( From e2c8a1edbcac9f876e019fc91d5adc4bacd7b114 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Wed, 17 Dec 2025 17:41:02 +0800 Subject: [PATCH 2/4] chore(release): 1.5.29 --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b13fb64f..39c41b88 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.29](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.28...v1.5.29) (2025-12-17) + ## [1.5.28](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.27...v1.5.28) (2025-12-17) ## [1.5.27](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.26...v1.5.27) (2025-12-17) diff --git a/package.json b/package.json index c81912d0..1a5856ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hipmi", - "version": "1.5.28", + "version": "1.5.29", "private": true, "prisma": { "seed": "bun prisma/seed.ts" From 6507bdcd35d17259ec4761638fa5b91c2e2bdc54 Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Fri, 19 Dec 2025 16:35:08 +0800 Subject: [PATCH 3/4] chore(release): 1.5.30 --- CHANGELOG.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39c41b88..566c8fac 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.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) ## [1.5.28](https://wibugit.wibudev.com/wibu/hipmi/compare/v1.5.27...v1.5.28) (2025-12-17) diff --git a/package.json b/package.json index 1a5856ad..35b74a7c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hipmi", - "version": "1.5.29", + "version": "1.5.30", "private": true, "prisma": { "seed": "bun prisma/seed.ts" From f05571caa49a8fb97f0690f9c43cd8457b96e59a Mon Sep 17 00:00:00 2001 From: bagasbanuna Date: Fri, 19 Dec 2025 16:38:33 +0800 Subject: [PATCH 4/4] Simpan notifikasi ke database Add: - prisma/migrations/20251218071503_add_type_on_db_notifikasi/ - src/app/api/mobile/notification/ Fix: - modified: prisma/schema.prisma - modified: src/app/api/mobile/auth/device-tokens/route.ts - deleted: src/app/api/mobile/notifications/route.ts - modified: x.sh ###No Issue --- .../migration.sql | 2 + prisma/schema.prisma | 1 + .../api/mobile/auth/device-tokens/route.ts | 3 + src/app/api/mobile/notification/[id]/route.ts | 49 +++++++ .../notification/[id]/unread-count/route.ts | 32 +++++ src/app/api/mobile/notification/route.ts | 124 ++++++++++++++++++ src/app/api/mobile/notifications/route.ts | 57 -------- x.sh | 7 +- 8 files changed, 216 insertions(+), 59 deletions(-) create mode 100644 prisma/migrations/20251218071503_add_type_on_db_notifikasi/migration.sql create mode 100644 src/app/api/mobile/notification/[id]/route.ts create mode 100644 src/app/api/mobile/notification/[id]/unread-count/route.ts create mode 100644 src/app/api/mobile/notification/route.ts delete mode 100644 src/app/api/mobile/notifications/route.ts diff --git a/prisma/migrations/20251218071503_add_type_on_db_notifikasi/migration.sql b/prisma/migrations/20251218071503_add_type_on_db_notifikasi/migration.sql new file mode 100644 index 00000000..67376820 --- /dev/null +++ b/prisma/migrations/20251218071503_add_type_on_db_notifikasi/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Notifikasi" ADD COLUMN "type" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b4134994..4d6ef14a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -987,6 +987,7 @@ model Notifikasi { 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 diff --git a/src/app/api/mobile/auth/device-tokens/route.ts b/src/app/api/mobile/auth/device-tokens/route.ts index 9d743e80..70a388f2 100644 --- a/src/app/api/mobile/auth/device-tokens/route.ts +++ b/src/app/api/mobile/auth/device-tokens/route.ts @@ -24,6 +24,9 @@ async function POST(request: NextRequest) { }, }); + + console.log("✅ EX", existing); + let deviceToken; if (existing) { diff --git a/src/app/api/mobile/notification/[id]/route.ts b/src/app/api/mobile/notification/[id]/route.ts new file mode 100644 index 00000000..fb446628 --- /dev/null +++ b/src/app/api/mobile/notification/[id]/route.ts @@ -0,0 +1,49 @@ +import { prisma } from "@/lib"; +import { NextRequest, NextResponse } from "next/server"; + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + const { id } = params; + const { searchParams } = new URL(request.url); + const category = searchParams.get("category"); + + try { + let fixData; + + 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", + }); + } + + return NextResponse.json({ + success: true, + data: fixData, + }); + } 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 new file mode 100644 index 00000000..1d32140c --- /dev/null +++ b/src/app/api/mobile/notification/[id]/unread-count/route.ts @@ -0,0 +1,32 @@ +import { prisma } from "@/lib"; +import { NextRequest, NextResponse } from "next/server"; + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + const { id } = params; + + console.log("Id >>", id); + + try { + const data = await prisma.notifikasi.findMany({ + where: { + userId: id, + isRead: false, + }, + }); + + console.log("Data >>", data); + + return NextResponse.json({ + success: true, + data: data.length, + }); + } catch (error) { + return NextResponse.json({ + success: false, + message: "Failed to get unread count", + }); + } +} diff --git a/src/app/api/mobile/notification/route.ts b/src/app/api/mobile/notification/route.ts new file mode 100644 index 00000000..92b5681a --- /dev/null +++ b/src/app/api/mobile/notification/route.ts @@ -0,0 +1,124 @@ +// app/api/test/notifications/route.ts +import { adminMessaging } from "@/lib/firebase-admin"; +import { NextRequest, NextResponse } from "next/server"; +import { prisma } from "@/lib"; + +export async function POST(request: NextRequest) { + try { + const { data } = await request.json(); + const { + fcmToken, + title, + body: notificationBody, + userLoginId, + type, + kategoriApp, + } = data; + + console.log("Data Notifikasi >>", data); + + if (!fcmToken || !title) { + return NextResponse.json( + { error: "Missing fcmToken or title" }, + { status: 400 } + ); + } + + const findUserLogin = await prisma.user.findUnique({ + where: { + id: userLoginId, + }, + }); + + if (!findUserLogin) { + return NextResponse.json({ error: "User not found" }, { status: 404 }); + } + + const deviceToken = await prisma.tokenUserDevice.findMany({ + where: { + isActive: true, + NOT: { + userId: findUserLogin.id, + }, + }, + include: { + user: true, + }, + }); + + 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 (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, + }, + }); + + if (createNotification) { + 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, + }, + }); + + if (createNotification) { + const response = await adminMessaging.send(message); + console.log("✅ FCM sent:", response); + } else { + return NextResponse.json({ + success: false, + message: "Failed to create notification", + }); + } + + } + } + + return NextResponse.json({ + success: true, + message: "Notification sent successfully", + }); + } catch (error: any) { + console.error("❌ FCM error:", error); + return NextResponse.json( + { error: error.message || "Failed to send FCM" }, + { status: 500 } + ); + } +} diff --git a/src/app/api/mobile/notifications/route.ts b/src/app/api/mobile/notifications/route.ts deleted file mode 100644 index 372d65e4..00000000 --- a/src/app/api/mobile/notifications/route.ts +++ /dev/null @@ -1,57 +0,0 @@ -// app/api/test/notifications/route.ts -import { adminMessaging } from "@/lib/firebase-admin"; -import { NextRequest, NextResponse } from "next/server"; - -export async function POST(request: NextRequest) { - try { - const { data } = await request.json(); - const { fcmToken, title, body: notificationBody, userLoginId } = data; - - console.log("Data Notifikasi >>", data); - - if (!fcmToken || !title) { - return NextResponse.json( - { error: "Missing fcmToken or title" }, - { status: 400 } - ); - } - - const deviceToken = await prisma.tokenUserDevice.findMany({ - where: { - isActive: true, - NOT: { - userId: userLoginId, - }, - }, - }); - - 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); - - const response = await adminMessaging.send(message); - console.log("✅ FCM sent:", response); - } - - return NextResponse.json({ - success: true, - message: "Notification sent successfully", - }); - } catch (error: any) { - console.error("❌ FCM error:", error); - return NextResponse.json( - { error: error.message || "Failed to send FCM" }, - { status: 500 } - ); - } -} diff --git a/x.sh b/x.sh index dd0f5f69..ef65dabf 100644 --- a/x.sh +++ b/x.sh @@ -3,10 +3,13 @@ URL="http://localhost:3000" # curl -X GET -H "Authorization: Bearer $TOKEN" ${URL}/api/middleware # curl -X GET -H "Cookie: hipmi-key=$TOKEN; user_id=789" ${URL}/dev/home | tee test.html -curl -X POST ${URL}/api/mobile/notifications \ +curl -X POST ${URL}/api/mobile/notification \ -H "Content-Type: application/json" \ -d '{ "fcmToken": "cVmHm-3P4E-1vjt6AA9kSF:APA91bHTkHjGTLxrFsb6Le6bZmzboZhwMGYXU4p0FP9yEeXixLDXNKS4F5vLuZV3sRgSnjjQsPpLOgstVLHJB8VJTObctKLdN-CxAp4dnP7Jbc_mH53jWvs", "title": "Test dari Backend (App Router)!", - "body": "Berhasil di App Router!" + "body": "Berhasil di App Router!", + "userLoginId": "cmha7p6yc0000cfoe5w2e7gdr", + "type": "NOTIFICATION", + "kategoriApp": "PERCOBAAN" }' \ No newline at end of file