diff --git a/src/app/api/monitoring/[[...slug]]/route.ts b/src/app/api/monitoring/[[...slug]]/route.ts index 3cbaf58..18ba934 100644 --- a/src/app/api/monitoring/[[...slug]]/route.ts +++ b/src/app/api/monitoring/[[...slug]]/route.ts @@ -1572,6 +1572,116 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) } ) + .get("/inactive-users", async ({ query, set }) => { + const VALID_DAYS = [7, 14, 30]; + const days = VALID_DAYS.includes(Number(query.days)) ? Number(query.days) : 7; + const pageNum = Number(query.page ?? 1) || 1; + const take = 15; + const { idVillage } = query; + + try { + const users = await prisma.user.findMany({ + where: { + idUserRole: { not: 'developer' }, + ...(idVillage && { idVillage }), + }, + select: { + id: true, + name: true, + nik: true, + email: true, + phone: true, + gender: true, + isActive: true, + isWithoutOTP: true, + isApprover: true, + idUserRole: true, + idVillage: true, + idGroup: true, + idPosition: true, + UserRole: { select: { name: true } }, + Village: { select: { id: true, name: true } }, + Group: { select: { name: true } }, + Position: { select: { name: true } }, + UserLog: { + orderBy: { createdAt: 'desc' }, + take: 1, + select: { createdAt: true }, + }, + }, + }); + + const threshold = new Date(Date.now() - days * 24 * 60 * 60 * 1000); + + const inactive = users + .map((u) => ({ + id: u.id, + name: u.name, + nik: u.nik, + email: u.email, + phone: u.phone, + gender: u.gender, + isActive: u.isActive, + isWithoutOTP: u.isWithoutOTP, + isApprover: u.isApprover, + role: u.UserRole?.name ?? null, + idUserRole: u.idUserRole, + village: u.Village?.name ?? null, + idVillage: u.Village?.id ?? null, + group: u.Group?.name ?? null, + position: u.Position?.name ?? null, + idGroup: u.idGroup, + idPosition: u.idPosition, + lastActivity: u.UserLog[0]?.createdAt ?? null, + daysSince: u.UserLog[0]?.createdAt + ? Math.floor((Date.now() - u.UserLog[0].createdAt.getTime()) / (1000 * 60 * 60 * 24)) + : null, + })) + .filter((u) => u.lastActivity === null || u.lastActivity < threshold) + .sort((a, b) => { + if (a.lastActivity === null) return -1; + if (b.lastActivity === null) return 1; + return a.lastActivity.getTime() - b.lastActivity.getTime(); + }); + + const total = inactive.length; + const paginated = inactive.slice((pageNum - 1) * take, pageNum * take); + + return { + success: true, + message: "Berhasil mendapatkan data", + data: { + users: paginated, + total, + totalPage: Math.ceil(total / take), + currentPage: pageNum, + days, + }, + }; + } catch (error) { + console.error("[inactive-users] 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)" })), + idVillage: t.Optional(t.String({ description: "Filter berdasarkan ID desa" })), + page: t.Optional(t.String({ description: "Halaman (default: 1)" })), + }), + detail: { + summary: "Inactive Users", + description: "Mendapatkan daftar user yang tidak ada aktivitas dalam X hari terakhir, atau belum pernah ada aktivitas sama sekali.", + tags: ["user"], + }, + } + ) + // ─── API KEY MANAGEMENT ────────────────────────────────────────────────── .get("/api-keys", async ({ set }) => {