From 6cf6486172b4dded7341a39bbc97826a2619e929 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Mon, 25 May 2026 12:00:53 +0800 Subject: [PATCH 1/3] feat: tambah endpoint GET /api-keys/:id untuk ambil full key --- src/app/api/monitoring/[[...slug]]/route.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/app/api/monitoring/[[...slug]]/route.ts b/src/app/api/monitoring/[[...slug]]/route.ts index f8785a6..ae6614b 100644 --- a/src/app/api/monitoring/[[...slug]]/route.ts +++ b/src/app/api/monitoring/[[...slug]]/route.ts @@ -1448,6 +1448,20 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) detail: { summary: "List API Keys", tags: ["api-key"] }, }) + .get("/api-keys/:id", async ({ params, set }) => { + try { + const key = await prisma.apiKey.findUnique({ where: { id: params.id } }); + if (!key) { set.status = 404; return { success: false, message: "API key tidak ditemukan" }; } + return { success: true, data: key }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan API key" }; + } + }, { + params: t.Object({ id: t.String() }), + detail: { summary: "Get API Key (full)", tags: ["api-key"] }, + }) + .post("/api-keys", async ({ body, set }) => { try { const rawKey = "ak_" + crypto.randomUUID().replace(/-/g, ""); -- 2.49.1 From 22555079f344fc8de1e5c3f3252e105af51fe455 Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Mon, 25 May 2026 15:08:30 +0800 Subject: [PATCH 2/3] feat: graph-log-villages support dateFrom/dateTo + recent-village-logs endpoint --- src/app/api/monitoring/[[...slug]]/route.ts | 160 ++++++++++++++------ 1 file changed, 117 insertions(+), 43 deletions(-) diff --git a/src/app/api/monitoring/[[...slug]]/route.ts b/src/app/api/monitoring/[[...slug]]/route.ts index ae6614b..2b3493a 100644 --- a/src/app/api/monitoring/[[...slug]]/route.ts +++ b/src/app/api/monitoring/[[...slug]]/route.ts @@ -633,7 +633,7 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) } ) .get("/graph-log-villages", async ({ query, set }) => { - const { id, time } = query; + const { id, time, dateFrom, dateTo } = query; try { const village = await prisma.village.findUnique({ @@ -651,14 +651,20 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) const now = new Date(); let startDate: Date; + let endDate: Date = now; + const useCustomRange = !!(dateFrom && dateTo); - if (time === "daily") { + if (useCustomRange) { + startDate = new Date(dateFrom); + endDate = new Date(dateTo); + endDate.setHours(23, 59, 59, 999); + } else if (time === "daily") { startDate = new Date(); startDate.setDate(now.getDate() - 13); // 14 hari } else if (time === "monthly") { startDate = new Date(now.getFullYear(), 0, 1); // awal tahun } else if (time === "yearly") { - startDate = new Date(now.getFullYear() - 4, 0, 1); // 5 tahun terakhir (opsional) + startDate = new Date(now.getFullYear() - 4, 0, 1); // 5 tahun terakhir } else { startDate = new Date(0); } @@ -667,6 +673,7 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) where: { createdAt: { gte: startDate, + ...(useCustomRange ? { lte: endDate } : {}), }, User: { idVillage: id, @@ -682,21 +689,27 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) // ========================= const map: Record = {}; + // Tentukan format label berdasarkan range + const effectiveTime = useCustomRange ? ( + // > 60 hari pakai monthly, selain itu daily + (endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24) > 60 ? 'monthly' : 'daily' + ) : time; + dataLog.forEach((log) => { const date = new Date(log.createdAt); - let label = ""; - if (time === "daily") { + if (effectiveTime === "daily") { label = date.toLocaleDateString("id-ID", { day: "2-digit", month: "short", }); - } else if (time === "monthly") { + } else if (effectiveTime === "monthly") { label = date.toLocaleDateString("id-ID", { month: "short", + year: "numeric", }); - } else if (time === "yearly") { + } else if (effectiveTime === "yearly") { label = date.getFullYear().toString(); } @@ -708,9 +721,13 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) // ========================= let result: any[] = []; - if (time === "daily") { - for (let i = 13; i >= 0; i--) { - const d = new Date(); + if (effectiveTime === "daily") { + const days = useCustomRange + ? Math.ceil((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)) + : 14; + + for (let i = days - 1; i >= 0; i--) { + const d = new Date(endDate); d.setDate(d.getDate() - i); const label = d.toLocaleDateString("id-ID", { @@ -723,41 +740,38 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) aktivitas: map[label] || 0, }); } - } else if (time === "monthly") { - const year = now.getFullYear(); - for (let m = 0; m <= 11; m++) { - const d = new Date(year, m, 1); + } else if (effectiveTime === "monthly") { + const s = new Date(startDate); + const e = new Date(endDate); + const months: string[] = []; - const label = d.toLocaleDateString("id-ID", { + const cursor = new Date(s.getFullYear(), s.getMonth(), 1); + while (cursor <= e) { + months.push(cursor.toLocaleDateString("id-ID", { month: "short", - }); - - result.push({ - label, - aktivitas: map[label] || 0, - }); + year: "numeric", + })); + cursor.setMonth(cursor.getMonth() + 1); } - } else if (time === "yearly") { + + result = months.map((label) => ({ + label, + aktivitas: map[label] || 0, + })); + } else if (effectiveTime === "yearly") { const years = Object.keys(map).map(Number); if (years.length === 0) { const currentYear = new Date().getFullYear(); - - result = [ - { label: currentYear.toString(), aktivitas: 0 } - ]; + result = [{ label: currentYear.toString(), aktivitas: 0 }]; } else { const minYear = Math.min(...years); const maxYear = Math.max(...years); - result = []; - for (let y = minYear; y <= maxYear; y++) { - const label = y.toString(); - result.push({ - label, - aktivitas: map[label] || 0, + label: y.toString(), + aktivitas: map[y.toString()] || 0, }); } } @@ -781,21 +795,81 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) { query: t.Object({ id: t.String({ description: "ID desa" }), - time: t.Enum( - { - daily: "daily", - monthly: "monthly", - yearly: "yearly", - }, - { - description: "Rentang waktu (daily = 14 hari, monthly = 1 tahun, yearly = per tahun)", - } - ), + time: t.Optional(t.Enum( + { daily: "daily", monthly: "monthly", yearly: "yearly" }, + { description: "Rentang waktu (daily = 14 hari, monthly = 1 tahun, yearly = per tahun). Default: daily" }, + )), + dateFrom: t.Optional(t.String({ description: "Filter dari tanggal (YYYY-MM-DD). Mengabaikan time jika diisi bersama dateTo." })), + dateTo: t.Optional(t.String({ description: "Filter sampai tanggal (YYYY-MM-DD). Mengabaikan time jika diisi bersama dateFrom." })), }), detail: { summary: "Graph Log Villages", description: - "Mendapatkan data grafik log aktivitas desa berdasarkan rentang waktu (harian, bulanan, tahunan)", + "Mendapatkan data grafik log aktivitas desa berdasarkan rentang waktu (harian, bulanan, tahunan) atau custom date range.", + tags: ["detail-villages"], + }, + } + ) + .get("/recent-village-logs", async ({ query, set }) => { + const { id } = query; + + try { + const village = await prisma.village.findUnique({ + where: { id }, + }); + + if (!village) { + set.status = 404; + return { + success: false, + message: "Desa tidak ditemukan", + data: null, + }; + } + + const logs = await prisma.userLog.findMany({ + where: { + User: { idVillage: id }, + }, + select: { + createdAt: true, + action: true, + desc: true, + User: { select: { name: true } }, + }, + orderBy: { createdAt: 'desc' }, + take: 10, + }); + + const result = logs.map((log) => ({ + timestamp: log.createdAt, + userName: log.User.name, + action: log.action, + desc: log.desc, + })); + + return { + success: true, + message: "Berhasil mendapatkan data", + data: result, + }; + } catch (error) { + console.error("[recent-village-logs] error:", error); + set.status = 500; + return { + success: false, + message: "Terjadi kesalahan pada server", + data: null, + }; + } + }, + { + query: t.Object({ + id: t.String({ description: "ID desa" }), + }), + detail: { + summary: "Recent Village Logs", + description: "Mendapatkan 10 log aktivitas terbaru di desa tertentu.", tags: ["detail-villages"], }, } -- 2.49.1 From 552957282b1638a667d2e1274dc2c913b2172b4b Mon Sep 17 00:00:00 2001 From: amaliadwiy Date: Mon, 25 May 2026 15:36:30 +0800 Subject: [PATCH 3/3] bump: version 0.1.18 + migration --- package.json | 2 +- prisma/migrations/20260525073630_auto/migration.sql | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/20260525073630_auto/migration.sql diff --git a/package.json b/package.json index 3486826..cebe27c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sistem-desa-mandiri", - "version": "0.1.17", + "version": "0.1.18", "private": true, "scripts": { "dev": "next dev --experimental-https", diff --git a/prisma/migrations/20260525073630_auto/migration.sql b/prisma/migrations/20260525073630_auto/migration.sql new file mode 100644 index 0000000..af5102c --- /dev/null +++ b/prisma/migrations/20260525073630_auto/migration.sql @@ -0,0 +1 @@ +-- This is an empty migration. \ No newline at end of file -- 2.49.1