From 1a20697f4cadd0aaebb3fe38ed4e80b34197adf2 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Wed, 11 Mar 2026 16:40:42 +0800 Subject: [PATCH] upd: api noc --- src/app/api/noc/[[...slug]]/route.ts | 503 +++++++++++++++++++++++++++ 1 file changed, 503 insertions(+) create mode 100644 src/app/api/noc/[[...slug]]/route.ts diff --git a/src/app/api/noc/[[...slug]]/route.ts b/src/app/api/noc/[[...slug]]/route.ts new file mode 100644 index 0000000..155449f --- /dev/null +++ b/src/app/api/noc/[[...slug]]/route.ts @@ -0,0 +1,503 @@ +import cors from "@elysiajs/cors"; +import { swagger } from "@elysiajs/swagger"; +import Elysia, { t } from "elysia"; +import { prisma } from "@/module/_global"; +import moment from "moment"; +import "moment/locale/id"; + +// Gabungkan semua ke dalam satu instance server yang dipasang di /api/noc +const NocServer = new Elysia({ prefix: "/api/noc" }) + .use(cors({ + origin: "*", + methods: ["GET", "POST", "OPTIONS"], + })) + .use(swagger({ + path: "/docs", // Karena prefix instance adalah /api/noc, maka ini akan diakses di /api/noc/docs + documentation: { + info: { + title: "Sistem Desa Mandiri - NOC API", + version: "1.0.0", + description: "API Khusus untuk kebutuhan NOC (Network Operation Center) dan Monitoring Desa", + }, + tags: [ + { name: "NOC", description: "Endpoint khusus monitoring" } + ] + } + })) + + // ── GET /api/noc/active-divisions ────────────────────────────────────────── + .get( + "/active-divisions", + async ({ query, set }) => { + const { idDesa, limit } = query; + + if (!idDesa) { + set.status = 400; + return { + success: false, + message: "Parameter idDesa wajib diisi", + data: null, + }; + } + + const maxResults = Number(limit ?? 5); + + try { + // Cek apakah desa ada + const village = await prisma.village.findUnique({ + where: { id: idDesa }, + select: { id: true, name: true }, + }); + + if (!village) { + set.status = 404; + return { + success: false, + message: "Desa tidak ditemukan", + data: null, + }; + } + + // Ambil semua divisi milik desa ini + const divisions = await prisma.division.findMany({ + where: { + idVillage: idDesa, + isActive: true, + }, + select: { + id: true, + name: true, + idGroup: true, + Group: { + select: { + name: true, + }, + }, + _count: { + select: { + DivisionProject: true, + }, + }, + }, + }); + + // Hitung total kegiatan per divisi & urutkan descending, ambil top sesuai limit + const ranked = divisions + .map((d) => ({ + id: d.id, + division: d.name, + group: d.Group.name, + totalKegiatan: d._count.DivisionProject + })) + .sort((a, b) => b.totalKegiatan - a.totalKegiatan) + .slice(0, maxResults); + + return { + success: true, + message: "Berhasil mendapatkan divisi teraktif", + data: { + idDesa: village.id, + namaDesa: village.name, + divisi: ranked, + }, + }; + } catch (error) { + console.error("[NOC] active-divisions error:", error); + set.status = 500; + return { + success: false, + message: "Terjadi kesalahan pada server", + data: null, + }; + } + }, + { + query: t.Object({ + idDesa: t.String({ description: "ID Desa yang ingin dicari" }), + limit: t.Optional(t.String({ description: "Jumlah maksimal data (default: 5)" })), + }), + detail: { + summary: "Divisi Teraktif", + description: "Mendapatkan daftar divisi teraktif berdasarkan jumlah proyek pada desa tertentu.", + tags: ["NOC"], + }, + } + ) + + // ── GET /api/noc/latest-projects ────────────────────────────────────────── + .get( + "/latest-projects", + async ({ query, set }) => { + const { idDesa, limit } = query; + + if (!idDesa) { + set.status = 400; + return { + success: false, + message: "Parameter idDesa wajib diisi", + data: null, + }; + } + + const maxResults = Math.min(Number(limit ?? 5), 50); + + try { + // Cek apakah desa ada + const village = await prisma.village.findUnique({ + where: { id: idDesa }, + select: { id: true, name: true }, + }); + + if (!village) { + set.status = 404; + return { + success: false, + message: "Desa tidak ditemukan", + data: null, + }; + } + + // Ambil proyek umum terbaru dari desa ini + const projects = await prisma.project.findMany({ + where: { + idVillage: idDesa, + isActive: true, + }, + select: { + id: true, + title: true, + status: true, + desc: true, + updatedAt: true, + Group: { + select: { + name: true, + }, + }, + User: { + select: { + name: true, + }, + }, + }, + orderBy: { + updatedAt: "desc", + }, + take: maxResults, + }); + + const mapped = projects.map((p) => ({ + id: p.id, + title: p.title, + status: p.status, + desc: p.desc, + group: p.Group.name, + createdBy: p.User.name, + updatedAt: p.updatedAt, + })); + + return { + success: true, + message: "Berhasil mendapatkan proyek terbaru", + data: { + idDesa: village.id, + namaDesa: village.name, + total: mapped.length, + projects: mapped, + }, + }; + } catch (error) { + console.error("[NOC] latest-projects error:", error); + set.status = 500; + return { + success: false, + message: "Terjadi kesalahan pada server", + data: null, + }; + } + }, + { + query: t.Object({ + idDesa: t.String({ description: "ID Desa yang ingin dicari" }), + limit: t.Optional( + t.String({ description: "Jumlah maksimal proyek (default: 5, maks: 50)" }) + ), + }), + detail: { + summary: "Latest Projects General", + description: "Mendapatkan daftar proyek umum terbaru dari berbagai grup pada desa tertentu.", + tags: ["NOC"], + }, + } + ) + + // ── GET /api/noc/village-summary ─────────────────────────────────────────── + .get( + "/village-summary", + async ({ query, set }) => { + const { idDesa } = query; + + if (!idDesa) { + set.status = 400; + return { success: false, message: "Parameter idDesa wajib diisi", data: null }; + } + + try { + const counts = await prisma.village.findUnique({ + where: { id: idDesa }, + select: { + name: true, + _count: { + select: { + User: true, + Group: true, + Division: true, + Project: true, + Announcement: true, + Discussion: true, + } + } + } + }); + + if (!counts) { + set.status = 404; + return { success: false, message: "Desa tidak ditemukan", data: null }; + } + + return { + success: true, + message: "Berhasil mendapatkan ringkasan desa", + data: { + idDesa, + namaDesa: counts.name, + summary: { + totalWarga: counts._count.User, + totalGrup: counts._count.Group, + totalDivisi: counts._count.Division, + totalProyek: counts._count.Project, + totalPengumuman: counts._count.Announcement, + totalDiskusi: counts._count.Discussion, + } + } + }; + } catch (error) { + console.error("[NOC] village-summary error:", error); + set.status = 500; + return { success: false, message: "Terjadi kesalahan pada server", data: null }; + } + }, + { + query: t.Object({ idDesa: t.String() }), + detail: { summary: "Village Summary Statistics", tags: ["NOC"] } + } + ) + + // ── GET /api/noc/recent-activity ─────────────────────────────────────────── + .get( + "/recent-activity", + async ({ query, set }) => { + const { idDesa, limit } = query; + + if (!idDesa) { + set.status = 400; + return { success: false, message: "Parameter idDesa wajib diisi", data: null }; + } + + const maxResults = Math.min(Number(limit ?? 10), 50); + + try { + const logs = await prisma.userLog.findMany({ + where: { + User: { + idVillage: idDesa + } + }, + select: { + id: true, + action: true, + desc: true, + createdAt: true, + User: { + select: { + name: true, + img: true, + Group: { select: { name: true } } + } + } + }, + orderBy: { createdAt: "desc" }, + take: maxResults + }); + + const mapped = logs.map(l => ({ + id: l.id, + userName: l.User.name, + userGroup: l.User.Group.name, + userImg: l.User.img, + action: l.action, + description: l.desc, + time: moment(l.createdAt).fromNow(), + date: moment(l.createdAt).format("YYYY-MM-DD HH:mm:ss") + })); + + return { + success: true, + message: "Berhasil mendapatkan aktivitas terbaru", + data: { idDesa, total: mapped.length, activities: mapped } + }; + } catch (error) { + console.error("[NOC] recent-activity error:", error); + set.status = 500; + return { success: false, message: "Terjadi kesalahan pada server", data: null }; + } + }, + { + query: t.Object({ + idDesa: t.String(), + limit: t.Optional(t.String()) + }), + detail: { summary: "Recent User Activity Logs", tags: ["NOC"] } + } + ) + + // ── GET /api/noc/upcoming-events ─────────────────────────────────────────── + .get( + "/upcoming-events", + async ({ query, set }) => { + const { idDesa, limit } = query; + + if (!idDesa) { + set.status = 400; + return { + success: false, + message: "Parameter idDesa wajib diisi", + data: null, + }; + } + + const maxResults = Math.min(Number(limit ?? 10), 50); + const today = moment().startOf("day").toDate(); + + try { + const village = await prisma.village.findUnique({ + where: { id: idDesa }, + select: { id: true, name: true }, + }); + + if (!village) { + set.status = 404; + return { + success: false, + message: "Desa tidak ditemukan", + data: null, + }; + } + + const events = await prisma.divisionCalendarReminder.findMany({ + where: { + isActive: true, + dateStart: { + gte: today, + }, + Division: { + idVillage: idDesa, + isActive: true, + }, + DivisionCalendar: { + isActive: true, + }, + }, + select: { + id: true, + idCalendar: true, + dateStart: true, + dateEnd: true, + timeStart: true, + timeEnd: true, + status: true, + Division: { + select: { + id: true, + name: true, + }, + }, + DivisionCalendar: { + select: { + title: true, + desc: true, + linkMeet: true, + repeatEventTyper: true, + User: { + select: { + name: true, + }, + }, + }, + }, + }, + orderBy: [ + { dateStart: "asc" }, + { timeStart: "asc" }, + ], + take: maxResults, + }); + + const mapped = events.map((e) => ({ + id: e.id, + idCalendar: e.idCalendar, + title: e.DivisionCalendar.title, + desc: e.DivisionCalendar.desc, + linkMeet: e.DivisionCalendar.linkMeet ?? null, + repeatEventTyper: e.DivisionCalendar.repeatEventTyper, + dateStart: moment(e.dateStart).format("YYYY-MM-DD"), + dateEnd: e.dateEnd + ? moment(e.dateEnd).format("YYYY-MM-DD") + : null, + timeStart: moment.utc(e.timeStart).format("HH:mm"), + timeEnd: moment.utc(e.timeEnd).format("HH:mm"), + status: e.status, + createdBy: e.DivisionCalendar.User.name, + divisi: { + id: e.Division.id, + name: e.Division.name, + }, + })); + + return { + success: true, + message: "Berhasil mendapatkan upcoming events", + data: { + idDesa: village.id, + namaDesa: village.name, + total: mapped.length, + events: mapped, + }, + }; + } catch (error) { + console.error("[NOC] upcoming-events error:", error); + set.status = 500; + return { + success: false, + message: "Terjadi kesalahan pada server", + data: null, + }; + } + }, + { + query: t.Object({ + idDesa: t.String({ description: "ID Desa yang ingin dicari" }), + limit: t.Optional( + t.String({ description: "Jumlah maksimal event (default: 10, maks: 50)" }) + ), + }), + detail: { + summary: "Upcoming Events", + description: "Mendapatkan daftar event yang akan datang untuk semua divisi pada desa tertentu.", + tags: ["NOC"], + }, + } + ); + +export const GET = NocServer.handle; +export const POST = NocServer.handle;