diff --git a/src/app/api/monitoring/[[...slug]]/route.ts b/src/app/api/monitoring/[[...slug]]/route.ts index 5672383..4b5b2ee 100644 --- a/src/app/api/monitoring/[[...slug]]/route.ts +++ b/src/app/api/monitoring/[[...slug]]/route.ts @@ -1510,6 +1510,66 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) } ) + .get("/stale-villages", async ({ query, set }) => { + const VALID_DAYS = [7, 14, 30]; + const days = VALID_DAYS.includes(Number(query.days)) ? Number(query.days) : 7; + + try { + const data = await prisma.$queryRaw` + SELECT + v."id", + v."name", + MAX(ul."createdAt") AS "lastActivity" + FROM "Village" v + LEFT JOIN "User" u ON u."idVillage" = v."id" AND u."idUserRole" != 'developer' + LEFT JOIN "UserLog" ul ON ul."idUser" = u."id" + WHERE v."isDummy" = false AND v."isActive" = true + GROUP BY v."id", v."name" + HAVING MAX(ul."createdAt") < NOW() - (${days} * INTERVAL '1 day') + OR MAX(ul."createdAt") IS NULL + ORDER BY "lastActivity" ASC NULLS FIRST + ` as any[]; + + const result = data.map((v: any) => ({ + id: v.id, + name: v.name, + lastActivity: v.lastActivity ?? null, + daysSince: v.lastActivity + ? Math.floor((Date.now() - new Date(v.lastActivity).getTime()) / (1000 * 60 * 60 * 24)) + : null, + })); + + return { + success: true, + message: "Berhasil mendapatkan data", + data: { + count: result.length, + days, + villages: result, + }, + }; + } catch (error) { + console.error("[stale-villages] error:", error); + set.status = 500; + return { + success: false, + message: "Terjadi kesalahan pada server", + data: null, + }; + } + }, + { + query: t.Object({ + days: t.Optional(t.String({ description: "Threshold hari tidak aktif: 7, 14, atau 30 (default: 7)" })), + }), + detail: { + summary: "Stale Villages", + description: "Mendapatkan daftar desa aktif yang tidak ada aktivitas dalam X hari terakhir (atau belum pernah ada aktivitas sama sekali).", + tags: ["villages"], + }, + } + ) + // ─── API KEY MANAGEMENT ────────────────────────────────────────────────── .get("/api-keys", async ({ set }) => {