diff --git a/prisma/migrations/20260406074103_add_dummy_village/migration.sql b/prisma/migrations/20260406074103_add_dummy_village/migration.sql new file mode 100644 index 0000000..ad69505 --- /dev/null +++ b/prisma/migrations/20260406074103_add_dummy_village/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Village" ADD COLUMN "isDummy" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 11fcbeb..c8c9f7f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -51,6 +51,7 @@ model Village { name String desc String @db.Text isActive Boolean @default(true) + isDummy Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt Group Group[] diff --git a/src/app/api/monitoring/[[...slug]]/route.ts b/src/app/api/monitoring/[[...slug]]/route.ts new file mode 100644 index 0000000..8845e15 --- /dev/null +++ b/src/app/api/monitoring/[[...slug]]/route.ts @@ -0,0 +1,233 @@ +import { prisma } from "@/module/_global"; +import cors from "@elysiajs/cors"; +import { swagger } from "@elysiajs/swagger"; +import Elysia from "elysia"; +import _ from "lodash"; +import moment from "moment"; +import "moment/locale/id"; + +// Gabungkan semua ke dalam satu instance server yang dipasang di /api/monitoring +const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) + .use(cors({ + origin: "*", + methods: ["GET", "POST", "OPTIONS"], + })) + .use(swagger({ + path: "/docs", // Karena prefix instance adalah /api/monitoring, maka ini akan diakses di /api/monitoring/docs + documentation: { + info: { + title: "Des Plus - Monitoring API", + version: "1.0.0", + description: "API Khusus untuk kebutuhan Dashboard Monitoring", + } + } + })) + + .get( + "/grid-overview", + async ({ query, set }) => { + try { + const version = await prisma.setting.findMany({ + select: { + id: true, + name: true, + value: true + } + }); + + const result_version = Object.fromEntries(version.map(item => [item.id, item.value])); + + const activity_today = await prisma.userLog.count({ + where: { + createdAt: { + gte: moment().subtract(1, 'days').toDate(), + lte: moment().toDate(), + } + } + }) + + const activity_yesterday = await prisma.userLog.count({ + where: { + createdAt: { + gte: moment().subtract(2, 'days').toDate(), + lte: moment().subtract(1, 'days').toDate(), + } + } + }) + + + const activity_increase = (activity_today - activity_yesterday); + const percentage_increase = (activity_increase / activity_yesterday) * 100 + + const total_village = await prisma.village.findMany({ + where: { + isDummy: false + } + }) + + const total_village_active = total_village.filter((item) => item.isActive).length + const total_village_inactive = total_village.filter((item) => !item.isActive).length + + return { + success: true, + message: "Berhasil mendapatkan data", + data: { + version: result_version, + activity: { + today: activity_today, + increase: _.isNaN(percentage_increase) ? 0 : percentage_increase.toFixed(2), + }, + village: { + active: total_village_active, + inactive: total_village_inactive, + }, + + }, + }; + } catch (error) { + console.error("[overview] grid-overview error:", error); + set.status = 500; + return { + success: false, + message: "Terjadi kesalahan pada server", + data: null, + }; + } + }, + { + detail: { + summary: "Grid Overview", + description: "Menu Overview - Mendapatkan daftar versi aplikasi.", + tags: ["overview"], + }, + } + ) + .get( + "/daily-activity", + async ({ query, set }) => { + try { + // const data = await prisma.userLog.findMany({ + // where: { + // User: { + // Village: { + // isDummy: false + // } + // }, + // createdAt: { + // gte: moment().subtract(7, 'days').toDate(), + // lte: moment().toDate(), + // } + // }, + // select: { + // createdAt: true, + // } + // }) + + const data = await prisma.$queryRaw` + SELECT + DATE(ul."createdAt") AS tanggal, + COUNT(*) AS total + FROM "UserLog" ul + JOIN "User" u ON ul."idUser" = u."id" + JOIN "Village" v ON u."idVillage" = v."id" + WHERE v."isDummy" = false + AND ul."createdAt" >= NOW() - INTERVAL '7 days' + GROUP BY tanggal + ORDER BY tanggal;` as any[]; + + const result = []; + + // ubah data ke map biar gampang lookup + const map = data.reduce((acc: any, item: any) => { + const key = moment(item.tanggal).format('YYYY-MM-DD'); + acc[key] = Number(item.total); + return acc; + }, {}); + + // generate 7 hari terakhir + for (let i = 6; i >= 0; i--) { + const date = moment().subtract(i, 'days'); + + const key = date.format('YYYY-MM-DD'); + + result.push({ + date: date.format('DD MMM'), + logs: map[key] || 0 + }); + } + + return { + success: true, + message: "Berhasil mendapatkan data", + data: result, + }; + } catch (error) { + console.error("[overview] daily-activity error:", error); + set.status = 500; + return { + success: false, + message: "Terjadi kesalahan pada server", + data: null, + }; + } + }, + { + detail: { + summary: "Daily Activity", + description: "Menu Overview - Mendapatkan data grafik aktivitas harian semua desa.", + tags: ["overview"], + }, + } + ) + .get( + "/comparison-activity", + async ({ query, set }) => { + try { + const data = await prisma.$queryRaw` + SELECT + v."name", + COUNT(ul."id") AS total_logs + FROM "UserLog" ul + JOIN "User" u ON ul."idUser" = u."id" + JOIN "Village" v ON u."idVillage" = v."id" + WHERE v."isDummy" = false + AND ul."createdAt" >= NOW() - INTERVAL '7 days' + GROUP BY v."id", v."name" + ORDER BY total_logs DESC; + ` as any[]; + + const result = data.map(item => ({ + village: item.name, + activity: Number(item.total_logs) + })); + + + return { + success: true, + message: "Berhasil mendapatkan data", + data: result, + }; + } catch (error) { + console.error("[overview] comparison-activity error:", error); + set.status = 500; + return { + success: false, + message: "Terjadi kesalahan pada server", + data: null, + }; + } + }, + { + detail: { + summary: "Comparison Activity", + description: "Menu Overview - Mendapatkan data grafik perbandingan aktivitas desa selama 7 hari terakhir.", + tags: ["overview"], + }, + } + ) + + ; + + +export const GET = MonitoringServer.handle; +export const POST = MonitoringServer.handle; diff --git a/src/app/api/version-app/route.ts b/src/app/api/version-app/route.ts index 6319559..54962f4 100644 --- a/src/app/api/version-app/route.ts +++ b/src/app/api/version-app/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; export async function GET(request: Request) { try { - return NextResponse.json({ success: true, version: "2.1.8", tahap: "beta", update: "-api untuk dashboard noc; -perbaikan otp" }, { status: 200 }); + return NextResponse.json({ success: true, version: "2.1.9", tahap: "beta", update: "-api untuk dashboard monitoring" }, { status: 200 }); } catch (error) { console.error(error); return NextResponse.json({ success: false, version: "Gagal mendapatkan version, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 });