diff --git a/.env.example b/.env.example index 0526ea8..c262303 100644 --- a/.env.example +++ b/.env.example @@ -48,6 +48,12 @@ WS_APIKEY="your-websocket-api-key" # API key untuk akses endpoint /api/monitoring (header: x-api-key) MONITORING_API_KEY="your-monitoring-api-key" +# =========================================== +# AI API +# =========================================== +# API key untuk akses endpoint /api/ai/* (header: x-api-key) +AI_API_KEY="your-ai-api-key" + # =========================================== # APPLICATION SETTINGS # =========================================== diff --git a/prisma/migrations/20260513060219_add_api_key/migration.sql b/prisma/migrations/20260513060219_add_api_key/migration.sql new file mode 100644 index 0000000..1669cb1 --- /dev/null +++ b/prisma/migrations/20260513060219_add_api_key/migration.sql @@ -0,0 +1,14 @@ +-- CreateTable +CREATE TABLE "ApiKey" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "key" TEXT NOT NULL, + "isActive" BOOLEAN NOT NULL DEFAULT true, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ApiKey_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "ApiKey_key_key" ON "ApiKey"("key"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ae87c5e..2bec87b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -729,3 +729,12 @@ model DivisionProjectTaskApproval { createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } + +model ApiKey { + id String @id @default(cuid()) + name String + key String @unique + isActive Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} diff --git a/src/app/api/ai/[[...slug]]/route.ts b/src/app/api/ai/[[...slug]]/route.ts new file mode 100644 index 0000000..491be13 --- /dev/null +++ b/src/app/api/ai/[[...slug]]/route.ts @@ -0,0 +1,1452 @@ +import { prisma } from "@/module/_global"; +import cors from "@elysiajs/cors"; +import { swagger } from "@elysiajs/swagger"; +import Elysia, { t } from "elysia"; +import _ from "lodash"; +import moment from "moment"; +import "moment/locale/id"; + +const CACHE_TTL_MS = 60_000; +let apiKeyCache: Set = new Set(); +let cacheExpiresAt = 0; + +async function isValidApiKey(incoming: string): Promise { + const now = Date.now(); + if (now > cacheExpiresAt) { + const rows = await prisma.apiKey.findMany({ where: { isActive: true }, select: { key: true } }); + apiKeyCache = new Set(rows.map((r) => r.key)); + cacheExpiresAt = now + CACHE_TTL_MS; + } + return apiKeyCache.has(incoming); +} + +const AiServer = new Elysia({ prefix: "/api/ai" }) + .use(cors({ + origin: "*", + methods: ["GET", "OPTIONS"], + allowedHeaders: ["Content-Type", "x-api-key"], + })) + .use(swagger({ + path: "/docs", + documentation: { + info: { + title: "Desa Plus - Jenna Perangkat Desa API", + version: "1.0.0", + description: "API untuk kebutuhan integrasi Jenna Perangkat Desa — data desa, divisi, proyek, dll.", + }, + components: { + securitySchemes: { + ApiKeyAuth: { + type: "apiKey", + in: "header", + name: "x-api-key", + }, + }, + }, + security: [{ ApiKeyAuth: [] }], + }, + })) + .onBeforeHandle(async ({ request, set, path }) => { + if (path.startsWith("/api/ai/docs")) return; + + const incoming = request.headers.get("x-api-key"); + if (!incoming || !(await isValidApiKey(incoming))) { + set.status = 401; + return { success: false, message: "Unauthorized" }; + } + }) + + // ─── ANNOUNCEMENT ──────────────────────────────────────────────────────── + + .get("/announcement", async ({ query, set }) => { + try { + const { search, page, get, desa, active } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + const data = await prisma.announcement.findMany({ + skip: dataSkip, + take: getFix, + where: { + idVillage: String(desa), + isActive: active === "false" ? false : true, + title: { contains: search ?? "", mode: "insensitive" }, + }, + orderBy: { createdAt: "desc" }, + }); + + return { success: true, message: "Berhasil mendapatkan pengumuman", data }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan pengumuman", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + search: t.Optional(t.String({ description: "Kata kunci judul" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + active: t.Optional(t.String({ description: "Filter aktif (true/false)" })), + }), + detail: { summary: "List Pengumuman", tags: ["announcement"] }, + }) + + .get("/announcement/:id", async ({ params, set }) => { + try { + const { id } = params; + + const count = await prisma.announcement.count({ where: { id } }); + if (count === 0) { + set.status = 404; + return { success: false, message: "Pengumuman tidak ditemukan" }; + } + + const announcement = await prisma.announcement.findUnique({ + where: { id }, + select: { id: true, title: true, desc: true }, + }); + + const announcementMember = await prisma.announcementMember.findMany({ + where: { idAnnouncement: id }, + select: { + idGroup: true, + idDivision: true, + Group: { select: { name: true } }, + Division: { select: { name: true } }, + }, + }); + + const member = announcementMember.map((v: any) => ({ + ..._.omit(v, ["Group", "Division"]), + group: v.Group.name, + division: v.Division.name, + })); + + return { success: true, message: "Berhasil mendapatkan pengumuman", data: { ...announcement, member } }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan pengumuman", reason: (error as Error).message }; + } + }, { + params: t.Object({ id: t.String({ description: "ID pengumuman" }) }), + detail: { summary: "Detail Pengumuman", tags: ["announcement"] }, + }) + + // ─── BANNER ────────────────────────────────────────────────────────────── + + .get("/banner", async ({ query, set }) => { + try { + const { search, page, get, desa, active } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + const data = await prisma.bannerImage.findMany({ + skip: dataSkip, + take: getFix, + where: { + idVillage: String(desa), + isActive: active === "false" ? false : true, + title: { contains: search ?? "", mode: "insensitive" }, + }, + orderBy: { createdAt: "desc" }, + }); + + return { success: true, message: "Berhasil mendapatkan banner", data }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan banner", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + search: t.Optional(t.String({ description: "Kata kunci judul" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + active: t.Optional(t.String({ description: "Filter aktif (true/false)" })), + }), + detail: { summary: "List Banner", tags: ["banner"] }, + }) + + .get("/banner/:id", async ({ params, set }) => { + try { + const { id } = params; + const data = await prisma.bannerImage.findUnique({ where: { id: String(id) } }); + return { success: true, message: "Berhasil mendapatkan banner", data }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan banner", reason: (error as Error).message }; + } + }, { + params: t.Object({ id: t.String({ description: "ID banner" }) }), + detail: { summary: "Detail Banner", tags: ["banner"] }, + }) + + // ─── CALENDAR ──────────────────────────────────────────────────────────── + + .get("/calendar", async ({ query, set }) => { + try { + const { division, date, desa, active, search, page, get } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = {}; + + const baseCalendar = { + title: { contains: search ?? "", mode: "insensitive" }, + isActive: active === "false" ? false : true, + Division: { idVillage: String(desa) }, + }; + + if (division) { + kondisi = { idDivision: division, ...(date && { dateStart: new Date(date) }), DivisionCalendar: baseCalendar }; + } else { + kondisi = { ...(date && { dateStart: new Date(date) }), DivisionCalendar: baseCalendar }; + } + + const data = await prisma.divisionCalendarReminder.findMany({ + where: kondisi, + skip: dataSkip, + take: getFix, + select: { + id: true, + dateStart: true, + timeStart: true, + timeEnd: true, + createdAt: true, + DivisionCalendar: { + select: { + isActive: true, + title: true, + desc: true, + User: { select: { name: true } }, + }, + }, + }, + orderBy: [{ dateStart: "asc" }, { timeStart: "asc" }, { timeEnd: "asc" }], + }); + + const result = data.map((v: any) => ({ + ..._.omit(v, ["DivisionCalendar"]), + title: v.DivisionCalendar.title, + desc: v.DivisionCalendar.desc, + createdBy: v.DivisionCalendar.User.name, + isActive: v.DivisionCalendar.isActive, + timeStart: moment.utc(v.timeStart).format("HH:mm"), + timeEnd: moment.utc(v.timeEnd).format("HH:mm"), + })); + + return { success: true, message: "Berhasil mendapatkan kalender", data: result }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan kalender", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + division: t.Optional(t.String({ description: "ID divisi" })), + date: t.Optional(t.String({ description: "Tanggal filter (ISO)" })), + search: t.Optional(t.String({ description: "Kata kunci judul" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + active: t.Optional(t.String({ description: "Filter aktif (true/false)" })), + }), + detail: { summary: "List Kalender", tags: ["calendar"] }, + }) + + .get("/calendar/:id", async ({ params, set }) => { + try { + const { id } = params; + + const count = await prisma.divisionCalendarReminder.count({ where: { id } }); + if (count === 0) { + set.status = 404; + return { success: false, message: "Acara tidak ditemukan" }; + } + + const data: any = await prisma.divisionCalendarReminder.findUnique({ + where: { id }, + select: { + id: true, + timeStart: true, + dateStart: true, + timeEnd: true, + createdAt: true, + DivisionCalendar: { + select: { + id: true, + title: true, + desc: true, + linkMeet: true, + repeatEventTyper: true, + repeatValue: true, + }, + }, + }, + }); + + const { DivisionCalendar, ...dataCalendar } = data; + const result = { + ...dataCalendar, + timeStart: moment.utc(dataCalendar.timeStart).format("HH:mm"), + timeEnd: moment.utc(dataCalendar.timeEnd).format("HH:mm"), + title: DivisionCalendar.title, + desc: DivisionCalendar.desc, + linkMeet: DivisionCalendar.linkMeet, + repeatEventTyper: DivisionCalendar.repeatEventTyper, + repeatValue: DivisionCalendar.repeatValue, + }; + + const memberRaw = await prisma.divisionCalendarMember.findMany({ + where: { idCalendar: DivisionCalendar.id }, + select: { + id: true, + idUser: true, + User: { select: { id: true, name: true, email: true, img: true } }, + }, + }); + + const member = memberRaw.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + email: v.User.email, + img: v.User.img, + })); + + return { success: true, message: "Berhasil mendapatkan kalender", data: { ...result, member } }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan kalender", reason: (error as Error).message }; + } + }, { + params: t.Object({ id: t.String({ description: "ID kalender reminder" }) }), + detail: { summary: "Detail Kalender", tags: ["calendar"] }, + }) + + // ─── DISCUSSION GENERAL ────────────────────────────────────────────────── + + .get("/discussion-general", async ({ query, set }) => { + try { + const { group, desa, search, page, status, active, get } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: active === "false" ? false : true, + status: status === "close" ? 2 : 1, + idVillage: String(desa), + title: { contains: !search || search === "null" ? "" : search, mode: "insensitive" }, + }; + + if (group && group !== "null") { + kondisi.idGroup = String(group); + } + + const data = await prisma.discussion.findMany({ + skip: dataSkip, + take: getFix, + where: kondisi, + orderBy: [{ status: "desc" }, { createdAt: "desc" }], + select: { + id: true, + title: true, + desc: true, + status: true, + createdAt: true, + DiscussionComment: { select: { id: true } }, + Group: { select: { name: true } }, + }, + }); + + const result = data.map((v: any) => ({ + ..._.omit(v, ["DiscussionComment", "status", "Group"]), + totalKomentar: v.DiscussionComment.length, + status: v.status === 1 ? "Open" : "Close", + group: v.Group.name, + })); + + return { success: true, message: "Berhasil mendapatkan diskusi", data: result }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan diskusi", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + group: t.Optional(t.String({ description: "ID group" })), + search: t.Optional(t.String({ description: "Kata kunci" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + status: t.Optional(t.String({ description: "Status: open/close" })), + active: t.Optional(t.String({ description: "Filter aktif (true/false)" })), + }), + detail: { summary: "List Diskusi Umum", tags: ["discussion-general"] }, + }) + + .get("/discussion-general/:id", async ({ params, query, set }) => { + try { + const { id } = params; + const { cat, desa } = query; + + const count = await prisma.discussion.count({ where: { id, idVillage: String(desa) } }); + if (count === 0) { + set.status = 404; + return { success: false, message: "Diskusi tidak ditemukan" }; + } + + if (cat === "comment") { + const data = await prisma.discussionComment.findMany({ + where: { idDiscussion: id, isActive: true }, + select: { + id: true, + comment: true, + createdAt: true, + idUser: true, + User: { select: { name: true, img: true } }, + }, + }); + const result = data.map((v: any) => ({ + ..._.omit(v, ["User"]), + username: v.User.name, + img: v.User.img, + })); + return { success: true, message: "Berhasil mendapatkan diskusi", data: result }; + } + + if (cat === "member") { + const data = await prisma.discussionMember.findMany({ + where: { idDiscussion: id, isActive: true }, + select: { idUser: true, User: { select: { name: true, img: true } } }, + }); + const result = data.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + img: v.User.img, + })); + return { success: true, message: "Berhasil mendapatkan diskusi", data: result }; + } + + const data = await prisma.discussion.findUnique({ + where: { id, idVillage: String(desa) }, + select: { + isActive: true, + id: true, + title: true, + idGroup: true, + desc: true, + status: true, + createdAt: true, + Group: { select: { name: true } }, + }, + }); + + return { + success: true, + message: "Berhasil mendapatkan diskusi", + data: { + id: data?.id, + isActive: data?.isActive, + idGroup: data?.idGroup, + group: data?.Group.name, + title: data?.title, + desc: data?.desc, + status: data?.status === 1 ? "Open" : "Close", + createdAt: data?.createdAt, + }, + }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan diskusi", reason: (error as Error).message }; + } + }, { + params: t.Object({ id: t.String({ description: "ID diskusi umum" }) }), + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + cat: t.Optional(t.String({ description: "Kategori: comment / member" })), + }), + detail: { summary: "Detail Diskusi Umum", tags: ["discussion-general"] }, + }) + + // ─── DISCUSSION (DIVISI) ───────────────────────────────────────────────── + + .get("/discussion", async ({ query, set }) => { + try { + const { division, search, page, status, active, desa, get } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: active === "false" ? false : true, + status: status === "close" ? 2 : 1, + Division: { idVillage: String(desa) }, + desc: { contains: !search || search === "null" ? "" : search, mode: "insensitive" }, + }; + + if (division && division !== "null") { + kondisi.idDivision = division; + } + + const data = await prisma.divisionDisscussion.findMany({ + skip: dataSkip, + take: getFix, + where: kondisi, + orderBy: { createdAt: "desc" }, + select: { + id: true, + desc: true, + status: true, + createdAt: true, + idDivision: true, + Division: { select: { name: true } }, + DivisionDisscussionComment: { select: { id: true } }, + }, + }); + + const result = data.map((v: any) => ({ + ..._.omit(v, ["DivisionDisscussionComment", "status", "Division"]), + totalKomentar: v.DivisionDisscussionComment.length, + status: v.status === 1 ? "Open" : "Close", + division: v.Division.name, + })); + + return { success: true, message: "Berhasil mendapatkan diskusi", data: result }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan diskusi", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + division: t.Optional(t.String({ description: "ID divisi" })), + search: t.Optional(t.String({ description: "Kata kunci" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + status: t.Optional(t.String({ description: "Status: open/close" })), + active: t.Optional(t.String({ description: "Filter aktif (true/false)" })), + }), + detail: { summary: "List Diskusi Divisi", tags: ["discussion"] }, + }) + + .get("/discussion/:id", async ({ params, set }) => { + try { + const { id } = params; + + const count = await prisma.divisionDisscussion.count({ where: { id } }); + if (count === 0) { + set.status = 404; + return { success: false, message: "Diskusi tidak ditemukan" }; + } + + const data = await prisma.divisionDisscussion.findUnique({ + where: { id }, + select: { + isActive: true, + id: true, + desc: true, + status: true, + createdAt: true, + idDivision: true, + Division: { select: { name: true } }, + User: { select: { name: true } }, + DivisionDisscussionComment: { + select: { + id: true, + comment: true, + createdAt: true, + User: { select: { name: true, img: true } }, + }, + }, + }, + }); + + if (!data) { + set.status = 404; + return { success: false, message: "Diskusi tidak ditemukan" }; + } + + const komentar = data.DivisionDisscussionComment.map((c: any) => ({ + id: c.id, + comment: c.comment, + createdAt: c.createdAt, + username: c.User.name, + userimg: c.User.img, + })); + + return { + success: true, + message: "Berhasil mendapatkan diskusi", + data: { + id: data.id, + idDivision: data.idDivision, + division: data.Division.name, + isActive: data.isActive, + desc: data.desc, + status: data.status === 1 ? "Open" : "Close", + createdAt: data.createdAt, + createdBy: data.User.name, + komentar, + }, + }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan diskusi", reason: (error as Error).message }; + } + }, { + params: t.Object({ id: t.String({ description: "ID diskusi divisi" }) }), + detail: { summary: "Detail Diskusi Divisi", tags: ["discussion"] }, + }) + + // ─── DIVISION ──────────────────────────────────────────────────────────── + + .get("/division", async ({ query, set }) => { + try { + const { desa, group, search, page, active, get } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: active === "false" ? false : true, + idVillage: String(desa), + name: { contains: !search || search === "null" ? "" : search, mode: "insensitive" }, + }; + + if (group && group !== "null") kondisi.idGroup = String(group); + + const data = await prisma.division.findMany({ + skip: dataSkip, + take: getFix, + where: kondisi, + select: { + id: true, + name: true, + desc: true, + idGroup: true, + Group: { select: { name: true } }, + DivisionMember: { where: { isActive: true }, select: { idUser: true } }, + }, + orderBy: { createdAt: "desc" }, + }); + + const result = data.map((v: any) => ({ + ..._.omit(v, ["DivisionMember", "Group"]), + group: v.Group.name, + jumlahMember: v.DivisionMember.length, + })); + + return { success: true, message: "Berhasil mendapatkan divisi", data: result }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan divisi", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + group: t.Optional(t.String({ description: "ID group" })), + search: t.Optional(t.String({ description: "Kata kunci nama" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + active: t.Optional(t.String({ description: "Filter aktif (true/false)" })), + }), + detail: { summary: "List Divisi", tags: ["division"] }, + }) + + .get("/division/report", async ({ query, set }) => { + try { + const { desa, group, division, "date-start": date, "date-end": dateAkhir, cat } = query; + + if (cat === "dokumen") { + let kondisi: any = { + isActive: true, + category: "FILE", + Division: { idVillage: String(desa) }, + createdAt: { gte: new Date(String(date)), lte: new Date(String(dateAkhir)) }, + }; + + if (group) kondisi.Division = { idGroup: String(group) }; + if (division) kondisi.idDivision = String(division); + + const dataDokumen = await prisma.divisionDocumentFolderFile.findMany({ where: kondisi }); + const image = ["jpg", "jpeg", "png", "heic"]; + let gambar = 0, dokumen = 0; + + _.map(_.groupBy(dataDokumen, "extension"), (v: any) => { + if (image.includes(v[0].extension)) gambar += v.length; + else dokumen += v.length; + }); + + return { success: true, message: "Berhasil mendapatkan data", data: { gambar, dokumen } }; + } + + if (cat === "event") { + const baseWhere = (dateFilter: any) => group + ? { isActive: true, Division: { idGroup: String(group) }, DivisionCalendarReminder: { some: dateFilter } } + : { isActive: true, Division: { idVillage: String(desa) }, DivisionCalendarReminder: { some: dateFilter } }; + + let selesaiWhere = baseWhere({ dateStart: { gte: new Date(String(date)), lte: new Date() } }); + let comingWhere = baseWhere({ dateStart: { gt: new Date(), lte: new Date(String(dateAkhir)) } }); + + if (division) { + selesaiWhere = { ...selesaiWhere, idDivision: String(division) } as any; + comingWhere = { ...comingWhere, idDivision: String(division) } as any; + } + + const [selesai, akan_datang] = await Promise.all([ + prisma.divisionCalendar.count({ where: selesaiWhere }), + prisma.divisionCalendar.count({ where: comingWhere }), + ]); + + return { success: true, message: "Berhasil mendapatkan data", data: { selesai, akan_datang } }; + } + + // default: progress + let kondisiProgress: any = { + isActive: true, + Division: group ? { idGroup: String(group) } : { idVillage: String(desa) }, + DivisionProjectTask: { + some: { + dateStart: { gte: new Date(String(date)) }, + dateEnd: { lte: new Date(String(dateAkhir)) }, + }, + }, + }; + + if (division) kondisiProgress.idDivision = String(division); + + const data = await prisma.divisionProject.groupBy({ where: kondisiProgress, by: ["status"], _count: true }); + const dataStatus = [ + { name: "Segera", status: 0 }, + { name: "Dikerjakan", status: 1 }, + { name: "Selesai", status: 2 }, + { name: "Dibatalkan", status: 3 }, + ]; + + const total = data.reduce((n, { _count }) => n + _count, 0); + const result = dataStatus.reduce((acc: any, ds) => { + const found = data.find((i: any) => i.status === ds.status); + const raw = found ? ((found._count * 100) / total).toFixed(2) : "0"; + const fix = raw !== "100.00" ? (raw.slice(-2) === "00" ? raw.slice(0, 2) : raw) : "100"; + acc[ds.name] = fix; + return acc; + }, {}); + + return { success: true, message: "Berhasil mendapatkan data", data: result }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan data", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + group: t.Optional(t.String({ description: "ID group" })), + division: t.Optional(t.String({ description: "ID divisi" })), + "date-start": t.Optional(t.String({ description: "Tanggal mulai (ISO)" })), + "date-end": t.Optional(t.String({ description: "Tanggal akhir (ISO)" })), + cat: t.Optional(t.String({ description: "Kategori: dokumen / event / (kosong = progress)" })), + }), + detail: { summary: "Laporan Divisi", tags: ["division"] }, + }) + + .get("/division/:id", async ({ params, query, set }) => { + try { + const { id } = params; + const { desa } = query; + + const data = await prisma.division.findUnique({ + where: { id: String(id), idVillage: String(desa) }, + }); + + if (!data) { + set.status = 404; + return { success: false, message: "Divisi tidak ditemukan" }; + } + + const memberRaw = await prisma.divisionMember.findMany({ + where: { idDivision: String(id), isActive: true }, + select: { + id: true, + isAdmin: true, + idUser: true, + User: { select: { name: true, img: true } }, + }, + orderBy: { isAdmin: "desc" }, + }); + + const member = memberRaw.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + img: v.User.img, + })); + + return { success: true, message: "Berhasil mendapatkan divisi", data: { ...data, member } }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan divisi", reason: (error as Error).message }; + } + }, { + params: t.Object({ id: t.String({ description: "ID divisi" }) }), + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + }), + detail: { summary: "Detail Divisi", tags: ["division"] }, + }) + + // ─── DOCUMENT ──────────────────────────────────────────────────────────── + + .get("/document", async ({ query, set }) => { + try { + const { division, desa, path, active, search, page, get } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + const pathFix = !path || path === "undefined" || path === "null" || path === "" ? "home" : path; + + let kondisi: any = { + Division: { idVillage: String(desa) }, + isActive: active === "false" ? false : true, + path: pathFix, + name: { contains: !search || search === "null" ? "" : search, mode: "insensitive" }, + }; + + if (division && division !== "null") kondisi.idDivision = String(division); + + let formatDataShare: any[] = []; + + if (pathFix === "home") { + const dataShare = await prisma.divisionDocumentShare.findMany({ + where: { isActive: true, idDivision: String(division), DivisionDocumentFolderFile: { isActive: true } }, + select: { + DivisionDocumentFolderFile: { + select: { + idStorage: true, + id: true, + category: true, + name: true, + extension: true, + path: true, + User: { select: { name: true } }, + createdAt: true, + updatedAt: true, + }, + }, + }, + orderBy: { DivisionDocumentFolderFile: { createdAt: "desc" } }, + }); + + formatDataShare = dataShare.map((v: any) => ({ + idStorage: v.DivisionDocumentFolderFile.idStorage, + id: v.DivisionDocumentFolderFile.id, + category: v.DivisionDocumentFolderFile.category, + name: v.DivisionDocumentFolderFile.name, + extension: v.DivisionDocumentFolderFile.extension, + path: v.DivisionDocumentFolderFile.path, + createdBy: v.DivisionDocumentFolderFile.User.name, + createdAt: v.DivisionDocumentFolderFile.createdAt, + updatedAt: v.DivisionDocumentFolderFile.updatedAt, + share: true, + })); + } + + const data = await prisma.divisionDocumentFolderFile.findMany({ + skip: dataSkip, + take: getFix, + where: kondisi, + select: { + id: true, + category: true, + name: true, + extension: true, + idStorage: true, + path: true, + User: { select: { name: true } }, + createdAt: true, + updatedAt: true, + }, + orderBy: { createdAt: "desc" }, + }); + + const allData = data.map((v: any) => ({ + ..._.omit(v, ["User"]), + createdBy: v.User.name, + share: false, + })); + + if (formatDataShare.length > 0) allData.push(...formatDataShare); + + return { + success: true, + message: "Berhasil mendapatkan item", + data: _.orderBy(allData, ["category", "createdAt"], ["desc", "desc"]), + }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan item", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + division: t.Optional(t.String({ description: "ID divisi" })), + path: t.Optional(t.String({ description: "Path folder (default: home)" })), + search: t.Optional(t.String({ description: "Kata kunci nama file" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + active: t.Optional(t.String({ description: "Filter aktif (true/false)" })), + }), + detail: { summary: "List Dokumen", tags: ["document"] }, + }) + + // ─── GROUP ─────────────────────────────────────────────────────────────── + + .get("/group", async ({ query, set }) => { + try { + const { desa, active, search, page, get } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + const data = await prisma.group.findMany({ + skip: dataSkip, + take: getFix, + where: { + isActive: active === "false" ? false : true, + idVillage: String(desa), + name: { contains: search ?? "", mode: "insensitive" }, + }, + orderBy: { name: "asc" }, + }); + + return { success: true, message: "Berhasil mendapatkan grup", data }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan grup", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + search: t.Optional(t.String({ description: "Kata kunci nama" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + active: t.Optional(t.String({ description: "Filter aktif (true/false)" })), + }), + detail: { summary: "List Group", tags: ["group"] }, + }) + + // ─── POSITION ──────────────────────────────────────────────────────────── + + .get("/position", async ({ query, set }) => { + try { + const { desa, group, active, search, page, get } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: active === "false" ? false : true, + Group: { idVillage: String(desa) }, + name: { contains: search ?? "", mode: "insensitive" }, + }; + + if (group && group !== "null") kondisi.idGroup = String(group); + + const positions = await prisma.position.findMany({ + skip: dataSkip, + take: getFix, + where: kondisi, + select: { + id: true, + name: true, + idGroup: true, + isActive: true, + createdAt: true, + updatedAt: true, + Group: { select: { name: true } }, + }, + orderBy: { name: "asc" }, + }); + + const result = positions.map((v: any) => ({ + ..._.omit(v, ["Group"]), + group: v.Group.name, + })); + + return { success: true, message: "Berhasil mendapatkan jabatan", data: result }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan jabatan", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + group: t.Optional(t.String({ description: "ID group" })), + search: t.Optional(t.String({ description: "Kata kunci nama" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + active: t.Optional(t.String({ description: "Filter aktif (true/false)" })), + }), + detail: { summary: "List Jabatan", tags: ["position"] }, + }) + + // ─── PROJECT ───────────────────────────────────────────────────────────── + + .get("/project", async ({ query, set }) => { + try { + const { desa, search, status, group, page, get } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: true, + idVillage: String(desa), + title: { contains: !search || search === "null" ? "" : search, mode: "insensitive" }, + }; + + if (status && status !== "null") { + const statusMap: Record = { segera: 0, dikerjakan: 1, selesai: 2, batal: 3 }; + kondisi.status = statusMap[status] ?? 0; + } + + if (group && group !== "null") kondisi.idGroup = String(group); + + const data = await prisma.project.findMany({ + skip: dataSkip, + take: getFix, + where: kondisi, + select: { + id: true, + idGroup: true, + title: true, + desc: true, + status: true, + ProjectMember: { where: { isActive: true }, select: { idUser: true } }, + ProjectTask: { where: { isActive: true }, select: { title: true, status: true } }, + Group: { select: { name: true } }, + }, + orderBy: { createdAt: "desc" }, + }); + + const result = data.map((v: any) => ({ + ..._.omit(v, ["ProjectMember", "ProjectTask", "status", "Group"]), + group: v.Group.name, + status: v.status === 1 ? "dikerjakan" : v.status === 2 ? "selesai" : v.status === 3 ? "batal" : "segera", + progress: _.ceil((v.ProjectTask.filter((i: any) => i.status === 1).length * 100) / v.ProjectTask.length), + member: v.ProjectMember.length, + })); + + return { success: true, message: "Berhasil mendapatkan kegiatan", data: result }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan kegiatan", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + group: t.Optional(t.String({ description: "ID group" })), + search: t.Optional(t.String({ description: "Kata kunci judul" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + status: t.Optional(t.String({ description: "Status: segera/dikerjakan/selesai/batal" })), + }), + detail: { summary: "List Kegiatan", tags: ["project"] }, + }) + + .get("/project/:id", async ({ params, query, set }) => { + try { + const { id } = params; + const { cat } = query; + + const data = await prisma.project.findUnique({ + where: { id: String(id) }, + select: { + id: true, idVillage: true, idGroup: true, title: true, status: true, + desc: true, reason: true, report: true, isActive: true, + Group: { select: { name: true } }, + }, + }); + + if (!data) { + set.status = 404; + return { success: false, message: "Kegiatan tidak ditemukan" }; + } + + if (cat === "data") { + const tasks = await prisma.projectTask.findMany({ where: { isActive: true, idProject: String(id) }, orderBy: { updatedAt: "desc" } }); + const selesai = _.filter(tasks, { status: 1 }).length; + const progress = Math.ceil((selesai / tasks.length) * 100); + return { + success: true, message: "Berhasil mendapatkan kegiatan", data: { + id: data.id, idVillage: data.idVillage, idGroup: data.idGroup, group: data.Group.name, + title: data.title, status: data.status === 3 ? "batal" : data.status === 2 ? "selesai" : data.status === 1 ? "dikerjakan" : "segera", + desc: data.desc, reason: data.reason, report: data.report, isActive: data.isActive, + progress: _.isNaN(progress) ? 0 : progress, + }, + }; + } + + if (cat === "task") { + const tasks = await prisma.projectTask.findMany({ + where: { isActive: true, idProject: String(id) }, + select: { id: true, title: true, desc: true, status: true, dateStart: true, dateEnd: true, createdAt: true }, + orderBy: { createdAt: "asc" }, + }); + return { + success: true, message: "Berhasil mendapatkan kegiatan", + data: _.orderBy(tasks.map((v: any) => ({ + ..._.omit(v, ["dateStart", "dateEnd", "createdAt", "status"]), + status: v.status === 1 ? "selesai" : "belum selesai", + dateStart: moment(v.dateStart).format("DD-MM-YYYY"), + dateEnd: moment(v.dateEnd).format("DD-MM-YYYY"), + createdAt: moment(v.createdAt).format("DD-MM-YYYY HH:mm"), + })), "createdAt", "asc"), + }; + } + + if (cat === "file") { + const files = await prisma.projectFile.findMany({ + where: { isActive: true, idProject: String(id) }, + orderBy: { createdAt: "asc" }, + select: { id: true, name: true, extension: true, idStorage: true }, + }); + return { success: true, message: "Berhasil mendapatkan kegiatan", data: files }; + } + + if (cat === "member") { + const members = await prisma.projectMember.findMany({ + where: { isActive: true, idProject: String(id) }, + select: { id: true, idUser: true, User: { select: { name: true, email: true, img: true, Position: { select: { name: true } } } } }, + }); + return { + success: true, message: "Berhasil mendapatkan kegiatan", + data: members.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, email: v.User.email, img: v.User.img, position: v.User.Position.name, + })), + }; + } + + if (cat === "link") { + const links = await prisma.projectLink.findMany({ where: { isActive: true, idProject: String(id) }, orderBy: { createdAt: "asc" } }); + return { success: true, message: "Berhasil mendapatkan kegiatan", data: links }; + } + + return { success: true, message: "Berhasil mendapatkan kegiatan", data: null }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan kegiatan", reason: (error as Error).message }; + } + }, { + params: t.Object({ id: t.String({ description: "ID kegiatan" }) }), + query: t.Object({ + cat: t.Optional(t.String({ description: "Kategori: data / task / file / member / link" })), + }), + detail: { summary: "Detail Kegiatan", tags: ["project"] }, + }) + + // ─── TASK (DIVISI) ─────────────────────────────────────────────────────── + + .get("/task", async ({ query, set }) => { + try { + const { desa, division, search, status, page, get } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: true, + Division: { idVillage: String(desa) }, + title: { contains: !search || search === "null" ? "" : search, mode: "insensitive" }, + }; + + if (status && status !== "null") { + const statusMap: Record = { segera: 0, dikerjakan: 1, selesai: 2, batal: 3 }; + kondisi.status = statusMap[status] ?? 0; + } + + if (division && division !== "null") kondisi.idDivision = String(division); + + const data = await prisma.divisionProject.findMany({ + skip: dataSkip, + take: getFix, + where: kondisi, + select: { + id: true, + idDivision: true, + title: true, + desc: true, + status: true, + DivisionProjectTask: { where: { isActive: true }, select: { title: true, status: true } }, + DivisionProjectMember: { where: { isActive: true }, select: { idUser: true } }, + Division: { select: { name: true } }, + }, + orderBy: { createdAt: "desc" }, + }); + + const result = data.map((v: any) => ({ + ..._.omit(v, ["DivisionProjectTask", "DivisionProjectMember", "status", "Division"]), + division: v.Division.name, + status: v.status === 1 ? "dikerjakan" : v.status === 2 ? "selesai" : v.status === 3 ? "batal" : "segera", + progress: _.ceil((v.DivisionProjectTask.filter((i: any) => i.status === 1).length * 100) / v.DivisionProjectTask.length), + member: v.DivisionProjectMember.length, + })); + + return { success: true, message: "Berhasil mendapatkan tugas divisi", data: result }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan tugas divisi", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + division: t.Optional(t.String({ description: "ID divisi" })), + search: t.Optional(t.String({ description: "Kata kunci judul" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + status: t.Optional(t.String({ description: "Status: segera/dikerjakan/selesai/batal" })), + }), + detail: { summary: "List Tugas Divisi", tags: ["task"] }, + }) + + .get("/task/:id", async ({ params, query, set }) => { + try { + const { id } = params; + const { cat } = query; + + const data = await prisma.divisionProject.findUnique({ + where: { id: String(id) }, + select: { + id: true, idDivision: true, title: true, status: true, + desc: true, reason: true, report: true, isActive: true, + Division: { select: { name: true } }, + }, + }); + + if (!data) { + set.status = 404; + return { success: false, message: "Tugas tidak ditemukan" }; + } + + if (cat === "data") { + const tasks = await prisma.divisionProjectTask.findMany({ where: { isActive: true, idProject: String(id) }, orderBy: { updatedAt: "desc" } }); + const selesai = _.filter(tasks, { status: 1 }).length; + const progress = Math.ceil((selesai / tasks.length) * 100); + return { + success: true, message: "Berhasil mendapatkan tugas divisi", data: { + id: data.id, idDivision: data.idDivision, division: data.Division.name, + title: data.title, status: data.status === 3 ? "batal" : data.status === 2 ? "selesai" : data.status === 1 ? "dikerjakan" : "segera", + desc: data.desc, reason: data.reason, report: data.report, isActive: data.isActive, progress, + }, + }; + } + + if (cat === "task") { + const tasks = await prisma.divisionProjectTask.findMany({ + where: { isActive: true, idProject: String(id) }, + select: { id: true, title: true, status: true, dateStart: true, dateEnd: true }, + orderBy: { createdAt: "asc" }, + }); + return { + success: true, message: "Berhasil mendapatkan tugas divisi", + data: tasks.map((v: any) => ({ + ..._.omit(v, ["dateStart", "dateEnd", "status"]), + status: v.status === 1 ? "selesai" : "belum selesai", + dateStart: moment(v.dateStart).format("DD-MM-YYYY"), + dateEnd: moment(v.dateEnd).format("DD-MM-YYYY"), + })), + }; + } + + if (cat === "file") { + const files = await prisma.divisionProjectFile.findMany({ + where: { isActive: true, idProject: String(id) }, + select: { id: true, ContainerFileDivision: { select: { id: true, name: true, extension: true, idStorage: true } } }, + }); + return { + success: true, message: "Berhasil mendapatkan tugas divisi", + data: files.map((v: any) => ({ + ..._.omit(v, ["ContainerFileDivision"]), + nameInStorage: v.ContainerFileDivision.id, + name: v.ContainerFileDivision.name, + extension: v.ContainerFileDivision.extension, + idStorage: v.ContainerFileDivision.idStorage, + })), + }; + } + + if (cat === "member") { + const members = await prisma.divisionProjectMember.findMany({ + where: { isActive: true, idProject: String(id) }, + select: { id: true, idUser: true, User: { select: { name: true, email: true, img: true, Position: { select: { name: true } } } } }, + }); + return { + success: true, message: "Berhasil mendapatkan tugas divisi", + data: members.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, email: v.User.email, img: v.User.img, position: v.User.Position.name, + })), + }; + } + + if (cat === "link") { + const links = await prisma.divisionProjectLink.findMany({ where: { isActive: true, idProject: String(id) }, orderBy: { createdAt: "asc" } }); + return { success: true, message: "Berhasil mendapatkan tugas divisi", data: links }; + } + + return { success: true, message: "Berhasil mendapatkan tugas divisi", data: null }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan tugas divisi", reason: (error as Error).message }; + } + }, { + params: t.Object({ id: t.String({ description: "ID tugas divisi" }) }), + query: t.Object({ + cat: t.Optional(t.String({ description: "Kategori: data / task / file / member / link" })), + }), + detail: { summary: "Detail Tugas Divisi", tags: ["task"] }, + }) + + // ─── USER ──────────────────────────────────────────────────────────────── + + .get("/user", async ({ query, set }) => { + try { + const { search, desa, group, active, page, get } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: active === "false" ? false : true, + idVillage: String(desa), + name: { contains: search ?? "", mode: "insensitive" }, + NOT: { idUserRole: "developer" }, + }; + + if (group && group !== "null") kondisi.idGroup = String(group); + + const users = await prisma.user.findMany({ + skip: dataSkip, + take: getFix, + where: kondisi, + select: { + id: true, + idUserRole: true, + isActive: true, + nik: true, + name: true, + phone: true, + Position: { select: { name: true } }, + Group: { select: { name: true } }, + }, + orderBy: { name: "asc" }, + }); + + const result = users.map((v: any) => ({ + ..._.omit(v, ["phone", "gender", "Group", "Position"]), + gender: v.gender === "F" ? "Perempuan" : "Laki-Laki", + phone: "+" + v.phone, + group: v.Group.name, + position: v?.Position?.name, + })); + + return { success: true, message: "Berhasil mendapatkan member", data: result }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan anggota", reason: (error as Error).message }; + } + }, { + query: t.Object({ + desa: t.Optional(t.String({ description: "ID desa" })), + group: t.Optional(t.String({ description: "ID group" })), + search: t.Optional(t.String({ description: "Kata kunci nama" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + active: t.Optional(t.String({ description: "Filter aktif (true/false)" })), + }), + detail: { summary: "List User", tags: ["user"] }, + }) + + .get("/user/:id", async ({ params, set }) => { + try { + const { id } = params; + + const user = await prisma.user.findUnique({ + where: { id }, + select: { + id: true, nik: true, name: true, phone: true, email: true, + gender: true, img: true, idGroup: true, isActive: true, + idPosition: true, createdAt: true, updatedAt: true, + UserRole: { select: { name: true, id: true } }, + Position: { select: { name: true, id: true } }, + Group: { select: { name: true, id: true } }, + }, + }); + + if (!user) { + set.status = 404; + return { success: false, message: "User tidak ditemukan" }; + } + + const result = _.omit( + { + ...user, + gender: user.gender === "F" ? "Perempuan" : "Laki-Laki", + phone: "+62" + user.phone, + group: user.Group.name, + position: user.Position?.name, + idUserRole: user.UserRole.id, + role: user.UserRole.name, + }, + ["Group", "Position", "UserRole"], + ); + + return { success: true, message: "Berhasil mendapatkan anggota", data: result }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan anggota", reason: (error as Error).message }; + } + }, { + params: t.Object({ id: t.String({ description: "ID user" }) }), + detail: { summary: "Detail User", tags: ["user"] }, + }) + + // ─── VILLAGE ───────────────────────────────────────────────────────────── + + .get("/village", async ({ query, set }) => { + try { + const { active, search, page, get } = query; + + const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get); + const dataSkip = !page ? 0 : Number(page) * getFix - getFix; + + const data = await prisma.village.findMany({ + skip: dataSkip, + take: getFix, + where: { + isActive: active === "false" ? false : true, + name: { contains: search ?? "", mode: "insensitive" }, + }, + select: { id: true, name: true, isActive: true, createdAt: true, updatedAt: true }, + orderBy: { name: "asc" }, + }); + + return { success: true, message: "Berhasil mendapatkan desa", data }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan desa", reason: (error as Error).message }; + } + }, { + query: t.Object({ + search: t.Optional(t.String({ description: "Kata kunci nama desa" })), + page: t.Optional(t.String({ description: "Halaman" })), + get: t.Optional(t.String({ description: "Jumlah data per halaman" })), + active: t.Optional(t.String({ description: "Filter aktif (true/false)" })), + }), + detail: { summary: "List Desa", tags: ["village"] }, + }); + +export const GET = AiServer.handle; +export const OPTIONS = AiServer.handle; diff --git a/src/app/api/ai/announcement/[id]/route.ts b/src/app/api/ai/announcement/[id]/route.ts deleted file mode 100644 index e301cc9..0000000 --- a/src/app/api/ai/announcement/[id]/route.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import { NextResponse } from "next/server"; - - -// GET ONE PENGUMUMAN, UNTUK TAMPIL DETAIL PENGUMUMAN -export async function GET(request: Request, context: { params: { id: string } }) { - try { - const { id } = context.params; - - const data = await prisma.announcement.count({ - where: { - id: id, - }, - }); - - if (data == 0) { - return NextResponse.json( - { - success: false, - message: "Gagal mendapatkan pengumuman, data tidak ditemukan", - }, - { status: 404 } - ); - } - - const announcement = await prisma.announcement.findUnique({ - where: { - id: id, - }, - select: { - id: true, - title: true, - desc: true, - }, - }); - - if (!announcement) { - return NextResponse.json( - { - success: false, - message: "Gagal mendapatkan pengumuman, data tidak ditemukan", - }, - { status: 404 } - ); - } - - let dataFix = { ...announcement, member: {} }; - - const announcementMember = await prisma.announcementMember.findMany({ - where: { - idAnnouncement: id, - }, - select: { - idGroup: true, - idDivision: true, - Group: { - select: { - name: true, - }, - }, - Division: { - select: { - name: true, - }, - }, - }, - }); - - const formatMember = announcementMember.map((v: any) => ({ - ..._.omit(v, ["Group", "Division"]), - idGroup: v.idGroup, - idDivision: v.idDivision, - group: v.Group.name, - division: v.Division.name - })) - - dataFix.member = formatMember - - return NextResponse.json( - { - success: true, - message: "Berhasil mendapatkan pengumuman", - data: dataFix, - }, - { status: 200 } - ); - - - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan pengumuman, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/announcement/route.ts b/src/app/api/ai/announcement/route.ts deleted file mode 100644 index a4a1d4a..0000000 --- a/src/app/api/ai/announcement/route.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import "moment/locale/id"; -import { NextResponse } from "next/server"; -export const dynamic = 'force-dynamic' - - - -// GET ALL PENGUMUMAN -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const judul = searchParams.get('search'); - const page = searchParams.get('page'); - const get = searchParams.get('get'); - const villageId = searchParams.get('desa'); - const active = searchParams.get('active'); - - let getFix = 0; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - - let kondisi: any = { - idVillage: String(villageId), - isActive: (active == "false" || active == undefined) ? false : true, - title: { - contains: (judul == undefined || judul == null) ? "" : judul, - mode: "insensitive" - } - } - - const data = await prisma.announcement.findMany({ - skip: dataSkip, - take: getFix, - where: kondisi, - orderBy: { - createdAt: 'desc' - } - }); - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan pengumuman", data, }, { status: 200 }); - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan pengumuman, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/banner/[id]/route.ts b/src/app/api/ai/banner/[id]/route.ts deleted file mode 100644 index 01f91ac..0000000 --- a/src/app/api/ai/banner/[id]/route.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { prisma } from "@/module/_global"; -import { NextResponse } from "next/server"; - - -// GET ONE BANNER -export async function GET(request: Request, context: { params: { id: string } }) { - try { - const { id } = context.params; - - const data = await prisma.bannerImage.findUnique({ - where: { - id: String(id) - } - }) - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan banner", data }, { status: 200 }); - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan banner, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/banner/route.ts b/src/app/api/ai/banner/route.ts deleted file mode 100644 index 93704b4..0000000 --- a/src/app/api/ai/banner/route.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import { NextResponse } from "next/server"; - - -// GET ALL BANNER -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const judul = searchParams.get('search'); - const page = searchParams.get('page'); - const get = searchParams.get('get'); - const villageId = searchParams.get('desa'); - const active = searchParams.get('active'); - - let getFix = 0; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - let kondisi: any = { - idVillage: String(villageId), - isActive: (active == "false" || active == undefined) ? false : true, - title: { - contains: (judul == undefined || judul == null) ? "" : judul, - mode: "insensitive" - } - } - - const data = await prisma.bannerImage.findMany({ - skip: dataSkip, - take: getFix, - where: kondisi, - orderBy: { - createdAt: 'desc' - } - }); - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan banner", data }, { status: 200 }); - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan data banner, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/calendar/[id]/route.ts b/src/app/api/ai/calendar/[id]/route.ts deleted file mode 100644 index 9d43c90..0000000 --- a/src/app/api/ai/calendar/[id]/route.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import moment from "moment"; -import { NextResponse } from "next/server"; - -// GET ONE CALENDER BY ID KALENDER REMINDER -export async function GET(request: Request, context: { params: { id: string } }) { - try { - const { id } = context.params - - const cek = await prisma.divisionCalendarReminder.count({ - where: { - id: id - } - }) - - if (cek == 0) { - return NextResponse.json( - { - success: false, - message: "Gagal mendapatkan acara, data tidak ditemukan", - }, - { status: 404 } - ); - } - - const data: any = await prisma.divisionCalendarReminder.findUnique({ - where: { - id: id - }, - select: { - id: true, - timeStart: true, - dateStart: true, - timeEnd: true, - createdAt: true, - DivisionCalendar: { - select: { - id: true, - title: true, - desc: true, - linkMeet: true, - repeatEventTyper: true, - repeatValue: true, - } - } - } - }); - const { DivisionCalendar, ...dataCalender } = data - const timeStart = moment.utc(dataCalender?.timeStart).format("HH:mm") - const timeEnd = moment.utc(dataCalender?.timeEnd).format("HH:mm") - const idCalendar = data?.DivisionCalendar.id - const title = data?.DivisionCalendar?.title - const desc = data?.DivisionCalendar?.desc - const linkMeet = data?.DivisionCalendar?.linkMeet - const repeatEventTyper = data?.DivisionCalendar?.repeatEventTyper - const repeatValue = data?.DivisionCalendar?.repeatValue - - - const result = { ...dataCalender, timeStart, timeEnd, title, desc, linkMeet, repeatEventTyper, repeatValue } - - - const member = await prisma.divisionCalendarMember.findMany({ - where: { - idCalendar - }, - select: { - id: true, - idUser: true, - User: { - select: { - id: true, - name: true, - email: true, - img: true - } - } - } - }) - const fixMember = member.map((v: any) => ({ - ..._.omit(v, ["User"]), - name: v.User.name, - email: v.User.email, - img: v.User.img - })) - - - const dataFix = { - ...result, - member: fixMember, - } - - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan kalender", data: dataFix }, { status: 200 }); - - } catch (error) { - return NextResponse.json({ success: false, message: "Gagal mendapatkan kalender, data tidak ditemukan (error: 500)", }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/calendar/route.ts b/src/app/api/ai/calendar/route.ts deleted file mode 100644 index c07d011..0000000 --- a/src/app/api/ai/calendar/route.ts +++ /dev/null @@ -1,149 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import moment from "moment"; -import "moment/locale/id"; -import { NextResponse } from "next/server"; - -//GET ALL CALENDER -export async function GET(request: Request) { - try { - - const { searchParams } = new URL(request.url); - const idDivision = searchParams.get("division"); - const isDate = searchParams.get("date") - const villageId = searchParams.get("desa") - const active = searchParams.get("active") - const search = searchParams.get("search") - const page = searchParams.get("page") - const get = searchParams.get("get") - - let getFix = 0; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - - let kondisi: any = {} - - if (idDivision != "" && idDivision != null && idDivision != undefined) { - if (isDate != null && isDate != undefined && isDate != "") { - kondisi = { - idDivision: String(idDivision), - dateStart: new Date(String(isDate)), - DivisionCalendar: { - title: { - contains: (search == undefined || search == null) ? "" : search, - mode: "insensitive" - }, - isActive: (active == "false" || active == undefined) ? false : true, - Division: { - idVillage: String(villageId) - } - } - } - } else { - kondisi = { - idDivision: String(idDivision), - DivisionCalendar: { - title: { - contains: (search == undefined || search == null) ? "" : search, - mode: "insensitive" - }, - isActive: (active == "false" || active == undefined) ? false : true, - Division: { - idVillage: String(villageId) - } - } - } - } - } else { - if (isDate != null && isDate != undefined && isDate != "") { - kondisi = { - dateStart: new Date(String(isDate)), - DivisionCalendar: { - title: { - contains: (search == undefined || search == null) ? "" : search, - mode: "insensitive" - }, - isActive: (active == "false" || active == undefined) ? false : true, - Division: { - idVillage: String(villageId) - } - } - } - } else { - kondisi = { - DivisionCalendar: { - title: { - contains: (search == undefined || search == null) ? "" : search, - mode: "insensitive" - }, - isActive: (active == "false" || active == undefined) ? false : true, - Division: { - idVillage: String(villageId) - } - } - } - } - } - - const data = await prisma.divisionCalendarReminder.findMany({ - where: kondisi, - skip: dataSkip, - take: getFix, - select: { - id: true, - dateStart: true, - timeStart: true, - timeEnd: true, - createdAt: true, - DivisionCalendar: { - select: { - isActive: true, - title: true, - desc: true, - User: { - select: { - name: true - } - } - } - } - }, - orderBy: [ - { - dateStart: 'asc' - }, - { - timeStart: 'asc' - }, - { - timeEnd: 'asc' - } - ] - }); - - const allOmit = data.map((v: any) => ({ - ..._.omit(v, ["DivisionCalendar", "User"]), - title: v.DivisionCalendar.title, - desc: v.DivisionCalendar.desc, - createdBy: v.DivisionCalendar.User.name, - isActive: v.DivisionCalendar.isActive, - timeStart: moment.utc(v.timeStart).format('HH:mm'), - timeEnd: moment.utc(v.timeEnd).format('HH:mm') - })) - - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan kalender", data: allOmit }, { status: 200 }); - - } catch (error) { - console.error(error) - return NextResponse.json({ success: false, message: "Gagal mendapatkan kalender, data tidak ditemukan (error: 500)" }, { status: 404 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/discussion-general/[id]/route.ts b/src/app/api/ai/discussion-general/[id]/route.ts deleted file mode 100644 index aae67dd..0000000 --- a/src/app/api/ai/discussion-general/[id]/route.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import { NextResponse } from "next/server"; - - -// GET ONE DETAIL DISKUSI UMUM -export async function GET(request: Request, context: { params: { id: string } }) { - try { - let dataFix - const { id } = context.params - - const { searchParams } = new URL(request.url); - const kategori = searchParams.get("cat"); - const idVillage = searchParams.get("desa"); - - const cek = await prisma.discussion.count({ - where: { - id, - idVillage: String(idVillage) - } - }) - - if (cek == 0) { - return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, data tidak ditemukan" }, { status: 404 }); - } - - if (kategori == "comment") { - const data = await prisma.discussionComment.findMany({ - where: { - idDiscussion: id, - isActive: true - }, - select: { - id: true, - comment: true, - createdAt: true, - idUser: true, - User: { - select: { - name: true, - img: true - } - } - } - }) - - dataFix = data.map((v: any) => ({ - ..._.omit(v, ["User",]), - username: v.User.name, - img: v.User.img - })) - - } else if (kategori == "member") { - const data = await prisma.discussionMember.findMany({ - where: { - idDiscussion: id, - isActive: true - }, - select: { - idUser: true, - User: { - select: { - name: true, - img: true - } - } - } - }) - - dataFix = data.map((v: any) => ({ - ..._.omit(v, ["User",]), - name: v.User.name, - img: v.User.img - })) - } else { - const data = await prisma.discussion.findUnique({ - where: { - id, - idVillage: String(idVillage) - }, - select: { - isActive: true, - id: true, - title: true, - idGroup: true, - desc: true, - status: true, - createdAt: true, - Group: { - select: { - name: true, - } - } - } - }) - - dataFix = { - id: data?.id, - isActive: data?.isActive, - idGroup: data?.idGroup, - group: data?.Group.name, - title: data?.title, - desc: data?.desc, - status: data?.status == 1 ? "Open" : "Close", - createdAt: data?.createdAt - } - } - - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan diskusi", data: dataFix }, { status: 200 }); - - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} diff --git a/src/app/api/ai/discussion-general/route.ts b/src/app/api/ai/discussion-general/route.ts deleted file mode 100644 index 1e26ea7..0000000 --- a/src/app/api/ai/discussion-general/route.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import "moment/locale/id"; -import { NextResponse } from "next/server"; - - -// GET ALL DISCUSSION GENERAL -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const idGroup = searchParams.get("group"); - const idVillage = searchParams.get("desa"); - const search = searchParams.get('search'); - const page = searchParams.get('page'); - const status = searchParams.get('status'); - const active = searchParams.get('active'); - const get = searchParams.get('get') - - - let getFix = 10; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - let kondisi: any = { - isActive: active == "false" ? false : true, - status: status == "close" ? 2 : 1, - idVillage: String(idVillage), - title: { - contains: (search == undefined || search == "null") ? "" : search, - mode: "insensitive" - }, - } - - if (idGroup != "null" && idGroup != undefined && idGroup != "") { - kondisi = { - ...kondisi, - idGroup: String(idGroup) - } - } - - - const data = await prisma.discussion.findMany({ - skip: dataSkip, - take: getFix, - where: kondisi, - orderBy: [ - { - status: 'desc' - }, - { - createdAt: 'desc' - } - ], - - select: { - id: true, - title: true, - desc: true, - status: true, - createdAt: true, - DiscussionComment: { - select: { - id: true, - } - }, - Group: { - select: { - name: true, - } - } - } - }); - - const fixData = data.map((v: any) => ({ - ..._.omit(v, ["DiscussionComment", "status", "Group"]), - totalKomentar: v.DiscussionComment.length, - status: v.status == 1 ? "Open" : "Close", - group: v.Group.name, - })) - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan diskusi", data: fixData }, { status: 200 }); - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} diff --git a/src/app/api/ai/discussion/[id]/route.ts b/src/app/api/ai/discussion/[id]/route.ts deleted file mode 100644 index c7d9876..0000000 --- a/src/app/api/ai/discussion/[id]/route.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { prisma } from "@/module/_global"; -import { NextResponse } from "next/server"; - -// GET ONE DISCUSSION BY ID -export async function GET(request: Request, context: { params: { id: string } }) { - try { - const { id } = context.params - - const cek = await prisma.divisionDisscussion.count({ - where: { id } - }) - - if (cek == 0) { - return NextResponse.json( - { success: false, message: "Gagal mendapatkan diskusi, data tidak ditemukan" }, - { status: 404 } - ); - } - - const data = await prisma.divisionDisscussion.findUnique({ - where: { id }, - select: { - isActive: true, - id: true, - desc: true, - status: true, - createdAt: true, - idDivision: true, - Division: { - select: { - name: true, - } - }, - User: { select: { name: true, img: true } }, - DivisionDisscussionComment: { - select: { - id: true, - comment: true, - createdAt: true, - User: { select: { name: true, img: true } } - } - }, - } - }); - - if (!data) { - return NextResponse.json( - { success: false, message: "Diskusi tidak ditemukan" }, - { status: 404 } - ); - } - - // ambil nama creator - const createdBy = data.User.name; - const status = data.status == 1 ? "Open" : "Close" - const division = data.Division.name - - // mapping komentar → hilangkan nested User - const komentar = data.DivisionDisscussionComment.map((comment: any) => ({ - id: comment.id, - comment: comment.comment, - createdAt: comment.createdAt, - username: comment.User.name, - userimg: comment.User.img, - })); - - // bentuk hasil akhir sesuai request - const result = { - id: data.id, - idDivision: data.idDivision, - division, - isActive: data.isActive, - desc: data.desc, - status, - createdAt: data.createdAt, - createdBy, - komentar, - }; - - return NextResponse.json( - { success: true, message: "Berhasil mendapatkan diskusi", data: result }, - { status: 200 } - ); - - } catch (error) { - console.error(error); - return NextResponse.json( - { success: false, message: "Gagal mendapatkan diskusi, coba lagi nanti (error: 500)", reason: (error as Error).message }, - { status: 500 } - ); - } -} diff --git a/src/app/api/ai/discussion/route.ts b/src/app/api/ai/discussion/route.ts deleted file mode 100644 index a37f0b9..0000000 --- a/src/app/api/ai/discussion/route.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import "moment/locale/id"; -import { NextResponse } from "next/server"; - - -// GET ALL DISCUSSION DIVISION ACTIVE = TRUE -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const idDivision = searchParams.get("division"); - const search = searchParams.get('search'); - const page = searchParams.get('page'); - const status = searchParams.get('status'); - const isActive = searchParams.get('active'); - const villageId = searchParams.get('desa'); - const get = searchParams.get('get'); - - let getFix = 0; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - let kondisi: any = { - isActive: isActive == "false" ? false : true, - status: status == "close" ? 2 : 1, - Division: { - idVillage: String(villageId) - }, - desc: { - contains: (search == undefined || search == "null") ? "" : search, - mode: "insensitive" - }, - } - - - if (idDivision != "null" && idDivision != null && idDivision != undefined) { - kondisi = { - isActive: isActive == "false" ? false : true, - status: status == "close" ? 2 : 1, - idDivision: idDivision, - Division: { - idVillage: String(villageId) - }, - desc: { - contains: (search == undefined || search == "null") ? "" : search, - mode: "insensitive" - }, - } - } - - const data = await prisma.divisionDisscussion.findMany({ - skip: dataSkip, - take: getFix, - where: kondisi, - orderBy: { - createdAt: 'desc' - }, - select: { - id: true, - desc: true, - status: true, - createdAt: true, - idDivision: true, - Division: { - select: { - name: true, - } - }, - DivisionDisscussionComment: { - select: { - id: true, - } - } - } - }); - - const fixData = data.map((v: any) => ({ - ..._.omit(v, ["DivisionDisscussionComment", "status", "Division"]), - totalKomentar: v.DivisionDisscussionComment.length, - status: v.status == 1 ? "Open" : "Close", - division: v.Division.name - })) - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan diskusi", data: fixData, }, { status: 200 }); - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/division/[id]/route.ts b/src/app/api/ai/division/[id]/route.ts deleted file mode 100644 index 5e5d475..0000000 --- a/src/app/api/ai/division/[id]/route.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import { NextResponse } from "next/server"; - - -// GET ONE DATA DIVISI :: UNTUK TAMPIL DATA DI HALAMAN EDIT DAN INFO -export async function GET(request: Request, context: { params: { id: string } }) { - try { - const { id } = context.params; - const { searchParams } = new URL(request.url); - const idVillage = searchParams.get("desa"); - - const data = await prisma.division.findUnique({ - where: { - id: String(id), - idVillage: String(idVillage) - } - }); - - if (!data) { - return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan", }, { status: 404 }); - } - - const member = await prisma.divisionMember.findMany({ - where: { - idDivision: String(id), - isActive: true, - }, - select: { - id: true, - isAdmin: true, - idUser: true, - User: { - select: { - name: true, - img: true - } - } - }, - orderBy: { - isAdmin: 'desc', - } - }) - - const fixMember = member.map((v: any) => ({ - ..._.omit(v, ["User"]), - name: v.User.name, - img: v.User.img - })) - - const dataFix = { - ...data, - member: fixMember - } - - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: dataFix, }, { status: 200 }); - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/division/report/route.ts b/src/app/api/ai/division/report/route.ts deleted file mode 100644 index 2d6bbee..0000000 --- a/src/app/api/ai/division/report/route.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import { NextResponse } from "next/server"; - -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url) - const idVillage = searchParams.get("desa") - const idGroup = searchParams.get("group") - const division = searchParams.get("division") - const date = searchParams.get("date-start") - const dateAkhir = searchParams.get("date-end") - const kat = searchParams.get("cat") - - - // CHART PROGRESS - if (kat == "dokumen") { - // CHART DOKUMEN - let kondisi: any = { - isActive: true, - category: 'FILE', - Division: { - idVillage: String(idVillage) - }, - createdAt: { - gte: new Date(String(date)), - lte: new Date(String(dateAkhir)) - }, - } - - if (idGroup != undefined && idGroup != null && idGroup != "") { - kondisi = { - isActive: true, - category: 'FILE', - Division: { - idGroup: String(idGroup) - }, - createdAt: { - gte: new Date(String(date)), - lte: new Date(String(dateAkhir)) - }, - } - } - - - if (division != undefined && division != null && division != "") { - kondisi = { - ...kondisi, - idDivision: String(division) - } - } - - - const dataDokumen = await prisma.divisionDocumentFolderFile.findMany({ - where: kondisi, - }) - - const groupData = _.map(_.groupBy(dataDokumen, "extension"), (v: any) => ({ - file: v[0].extension, - jumlah: v.length, - })) - - const image = ['jpg', 'jpeg', 'png', 'heic'] - - let hasilImage = { - name: 'Gambar', - value: 0 - } - - let hasilFile = { - name: 'Dokumen', - value: 0 - } - - groupData.map((v: any) => { - if (image.some((i: any) => i == v.file)) { - hasilImage = { - name: 'Gambar', - value: hasilImage.value + v.jumlah - } - } else { - hasilFile = { - name: 'Dokumen', - value: hasilFile.value + v.jumlah - } - } - }) - - const hasilDokumen = { gambar: hasilImage.value, dokumen: hasilFile.value } - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan data", data: hasilDokumen }, { status: 200 }); - } else if (kat == "event") { - // CHART EVENT - let kondisiSelesai: any = { - isActive: true, - Division: { - idVillage: String(idVillage) - }, - DivisionCalendarReminder: { - some: { - dateStart: { - gte: new Date(String(date)), - lte: new Date() - } - } - } - } - - let kondisiComingSoon: any = { - isActive: true, - Division: { - idVillage: String(idVillage) - }, - DivisionCalendarReminder: { - some: { - dateStart: { - gt: new Date(), - lte: new Date(String(dateAkhir)) - } - } - } - } - - if (idGroup != undefined && idGroup != null && idGroup != "") { - kondisiSelesai = { - isActive: true, - Division: { - idGroup: String(idGroup) - }, - DivisionCalendarReminder: { - some: { - dateStart: { - gte: new Date(String(date)), - lte: new Date() - } - } - } - } - - kondisiComingSoon = { - isActive: true, - Division: { - idGroup: String(idGroup) - }, - DivisionCalendarReminder: { - some: { - dateStart: { - gt: new Date(), - lte: new Date(String(dateAkhir)) - } - } - } - } - } - - if (division != undefined && division != null && division != "") { - kondisiSelesai = { - ...kondisiSelesai, - idDivision: String(division) - } - - kondisiComingSoon = { - ...kondisiComingSoon, - idDivision: String(division) - } - } - - const eventSelesai = await prisma.divisionCalendar.count({ - where: kondisiSelesai - }) - - const eventComingSoon = await prisma.divisionCalendar.count({ - where: kondisiComingSoon - }) - - const hasilEvent = { - selesai: eventSelesai, - akan_datang: eventComingSoon - } - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan data", data: hasilEvent }, { status: 200 }); - - } else { - let kondisiProgress: any = { - isActive: true, - Division: { - idVillage: String(idVillage) - }, - DivisionProjectTask: { - some: { - dateStart: { - gte: new Date(String(date)) - }, - dateEnd: { - lte: new Date(String(dateAkhir)) - } - } - } - } - - if (idGroup != undefined && idGroup != null && idGroup != "") { - kondisiProgress = { - isActive: true, - Division: { - idGroup: String(idGroup) - }, - DivisionProjectTask: { - some: { - dateStart: { - gte: new Date(String(date)) - }, - dateEnd: { - lte: new Date(String(dateAkhir)) - } - } - } - } - } - - if (division != undefined && division != null && division != "") { - kondisiProgress = { - ...kondisiProgress, - idDivision: String(division) - } - } - - const data = await prisma.divisionProject.groupBy({ - where: kondisiProgress, - by: ["status"], - _count: true - }) - - const dataStatus = [{ name: 'Segera', status: 0 }, { name: 'Dikerjakan', status: 1 }, { name: 'Selesai', status: 2 }, { name: 'Dibatalkan', status: 3 }] - const hasilProgres: any[] = [] - let input - for (let index = 0; index < dataStatus.length; index++) { - const cek = data.some((i: any) => i.status == dataStatus[index].status) - if (cek) { - const find = ((Number(data.find((i: any) => i.status == dataStatus[index].status)?._count) * 100) / data.reduce((n, { _count }) => n + _count, 0)).toFixed(2) - const fix = find != "100.00" ? find.substr(-2, 2) == "00" ? find.substr(0, 2) : find : "100" - input = { - name: dataStatus[index].name, - value: fix - } - } else { - input = { - name: dataStatus[index].name, - value: 0 - } - } - hasilProgres.push(input) - } - - const dataFixProgress = hasilProgres.reduce((acc: any, curr: any) => { - acc[curr.name] = curr.value - return acc - }, {}) - - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan data", data: dataFixProgress }, { status: 200 }); - - } - - } - catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan data, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/division/route.ts b/src/app/api/ai/division/route.ts deleted file mode 100644 index 7c65213..0000000 --- a/src/app/api/ai/division/route.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import { NextResponse } from "next/server"; - - -// GET ALL DATA DIVISI == LIST DATA DIVISI -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const idVillage = searchParams.get("desa"); - const idGroup = searchParams.get("group"); - const name = searchParams.get('search'); - const page = searchParams.get('page'); - const active = searchParams.get("active"); - const get = searchParams.get('get') - - let getFix = 10; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - let kondisi: any = { - isActive: active == 'false' ? false : true, - idVillage: String(idVillage), - name: { - contains: (name == undefined || name == "null") ? "" : name, - mode: "insensitive" - } - } - - if (idGroup != "null" && idGroup != undefined && idGroup != "") { - kondisi = { - ...kondisi, - idGroup: String(idGroup) - } - } - - - const data = await prisma.division.findMany({ - skip: dataSkip, - take: getFix, - where: kondisi, - select: { - id: true, - name: true, - desc: true, - idGroup: true, - Group: { - select: { - name: true - } - }, - DivisionMember: { - where: { - isActive: true - }, - select: { - idUser: true - } - } - }, - orderBy: { - createdAt: 'desc' - } - }); - - const allData = data.map((v: any) => ({ - ..._.omit(v, ["DivisionMember", "Group"]), - group: v.Group.name, - jumlahMember: v.DivisionMember.length, - })) - - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: allData }, { status: 200 }); - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/document/route.ts b/src/app/api/ai/document/route.ts deleted file mode 100644 index 2c52ec7..0000000 --- a/src/app/api/ai/document/route.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import moment from "moment"; -import { NextResponse } from "next/server"; - - -// GET ALL DOCUMENT -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const idDivision = searchParams.get("division"); - const villageId = searchParams.get("desa"); - const path = searchParams.get("path"); - const active = searchParams.get("active"); - const search = searchParams.get("search"); - const page = searchParams.get("page"); - const get = searchParams.get("get"); - - let getFix = 10; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - let kondisi: any = { - Division: { - idVillage: String(villageId) - }, - isActive: active == 'false' ? false : true, - path: (path == "undefined" || path == "null" || path == "" || path == null || path == undefined) ? "home" : path, - name: { - contains: (search == undefined || search == "null") ? "" : search, - mode: "insensitive" - } - } - - if (idDivision != "null" && idDivision != undefined && idDivision != "") { - kondisi = { - ...kondisi, - idDivision: String(idDivision) - } - } - - let formatDataShare: any[] = []; - - if (path == "home" || path == "null" || path == "undefined" || path == null || path == undefined || path == "") { - const dataShare = await prisma.divisionDocumentShare.findMany({ - where: { - isActive: true, - idDivision: String(idDivision), - DivisionDocumentFolderFile: { - isActive: true - } - }, - select: { - DivisionDocumentFolderFile: { - select: { - idStorage: true, - id: true, - category: true, - name: true, - extension: true, - path: true, - User: { - select: { - name: true - } - }, - createdAt: true, - updatedAt: true - } - } - }, - orderBy: { - DivisionDocumentFolderFile: { - createdAt: 'desc' - } - } - }) - - formatDataShare = dataShare.map((v: any) => ({ - ..._.omit(v, ["DivisionDocumentFolderFile"]), - idStorage: v.DivisionDocumentFolderFile.idStorage, - id: v.DivisionDocumentFolderFile.id, - category: v.DivisionDocumentFolderFile.category, - name: v.DivisionDocumentFolderFile.name, - extension: v.DivisionDocumentFolderFile.extension, - path: v.DivisionDocumentFolderFile.path, - createdBy: v.DivisionDocumentFolderFile.User.name, - createdAt: v.DivisionDocumentFolderFile.createdAt, - updatedAt: v.DivisionDocumentFolderFile.updatedAt, - share: true - })) - } - - - const data = await prisma.divisionDocumentFolderFile.findMany({ - skip: dataSkip, - take: getFix, - where: kondisi, - select: { - id: true, - category: true, - name: true, - extension: true, - idStorage: true, - path: true, - User: { - select: { - name: true - } - }, - createdAt: true, - updatedAt: true - }, - orderBy: { - createdAt: 'desc' - } - }) - - const allData = data.map((v: any) => ({ - ..._.omit(v, ["User", "createdAt", "updatedAt"]), - createdBy: v.User.name, - createdAt: v.createdAt, - updatedAt: v.updatedAt, - share: false - })) - - if (formatDataShare.length > 0) { - allData.push(...formatDataShare) - } - - const formatData = _.orderBy(allData, ['category', 'createdAt'], ['desc', 'desc']); - - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan item", data: formatData }, { status: 200 }); - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan item, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/group/route.ts b/src/app/api/ai/group/route.ts deleted file mode 100644 index 3b85c37..0000000 --- a/src/app/api/ai/group/route.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import { NextResponse } from "next/server"; - -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const villageId = searchParams.get("desa"); - const isActive = searchParams.get("active"); - const search = searchParams.get('search'); - const page = searchParams.get('page') - const get = searchParams.get('get') - - let getFix = 10; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - const data = await prisma.group.findMany({ - skip: dataSkip, - take: getFix, - where: { - isActive: isActive == 'false' ? false : true, - idVillage: String(villageId), - name: { - contains: (search == undefined || search == null) ? "" : search, - mode: "insensitive" - } - }, - orderBy: { - name: 'asc' - } - }); - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan grup", data, }, { status: 200 }); - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan grup, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/position/route.ts b/src/app/api/ai/position/route.ts deleted file mode 100644 index 3aaca1f..0000000 --- a/src/app/api/ai/position/route.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import { NextResponse } from "next/server"; - - -// GET ALL POSITION -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const idVillage = searchParams.get("desa"); - const idGroup = searchParams.get("group"); - const active = searchParams.get('active'); - const search = searchParams.get('search') - const page = searchParams.get('page') - const get = searchParams.get('get') - - let getFix = 10; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - let kondisi: any = { - isActive: active == 'false' ? false : true, - Group: { - idVillage: String(idVillage) - }, - name: { - contains: (search == undefined || search == null) ? "" : search, - mode: "insensitive" - } - } - - if (idGroup != "null" && idGroup != undefined && idGroup != "") { - kondisi = { - ...kondisi, - idGroup: String(idGroup) - } - } - - - - const positions = await prisma.position.findMany({ - skip: dataSkip, - take: getFix, - where: kondisi, - select: { - id: true, - name: true, - idGroup: true, - isActive: true, - createdAt: true, - updatedAt: true, - Group: { - select: { - name: true - } - } - }, - orderBy: { - name: 'asc' - } - }); - - const allData = positions.map((v: any) => ({ - ..._.omit(v, ["Group"]), - group: v.Group.name - })) - - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan jabatan", data: allData }, { status: 200 }); - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan jabatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/project/[id]/route.ts b/src/app/api/ai/project/[id]/route.ts deleted file mode 100644 index 83853a9..0000000 --- a/src/app/api/ai/project/[id]/route.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import moment from "moment"; -import { NextResponse } from "next/server"; - - -// GET DETAIL PROJECT / GET ONE PROJECT -export async function GET(request: Request, context: { params: { id: string } }) { - try { - let allData - const { id } = context.params; - const { searchParams } = new URL(request.url); - const kategori = searchParams.get("cat"); - - const data = await prisma.project.findUnique({ - where: { - id: String(id), - }, - select: { - id: true, - idVillage: true, - idGroup: true, - title: true, - status: true, - desc: true, - reason: true, - report: true, - isActive: true, - Group: { - select: { - name: true - } - } - } - }); - - if (!data) { - return NextResponse.json({ success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", }, { status: 404 }); - } - - - if (kategori == "data") { - const dataProgress = await prisma.projectTask.findMany({ - where: { - isActive: true, - idProject: String(id) - }, - orderBy: { - updatedAt: 'desc' - } - }) - - const semua = dataProgress.length - const selesai = _.filter(dataProgress, { status: 1 }).length - const progress = Math.ceil((selesai / semua) * 100) - - allData = { - id: data.id, - idVillage: data.idVillage, - idGroup: data.idGroup, - group: data.Group.name, - title: data.title, - status: data.status == 3 ? "batal" : data.status == 2 ? "selesai" : data.status == 1 ? "dikerjakan" : "segera", - desc: data.desc, - reason: data.reason, - report: data.report, - isActive: data.isActive, - progress: (_.isNaN(progress)) ? 0 : progress, - } - - } else if (kategori == "task") { - const dataProgress = await prisma.projectTask.findMany({ - where: { - isActive: true, - idProject: String(id) - }, - select: { - id: true, - title: true, - desc: true, - status: true, - dateStart: true, - dateEnd: true, - createdAt: true - }, - orderBy: { - createdAt: 'asc' - } - }) - - const formatData = dataProgress.map((v: any) => ({ - ..._.omit(v, ["dateStart", "dateEnd", "createdAt", "status"]), - status: v.status == 1 ? "selesai" : "belum selesai", - dateStart: moment(v.dateStart).format("DD-MM-YYYY"), - dateEnd: moment(v.dateEnd).format("DD-MM-YYYY"), - createdAt: moment(v.createdAt).format("DD-MM-YYYY HH:mm"), - })) - const dataFix = _.orderBy(formatData, 'createdAt', 'asc') - allData = dataFix - - } else if (kategori == "file") { - const dataFile = await prisma.projectFile.findMany({ - where: { - isActive: true, - idProject: String(id) - }, - orderBy: { - createdAt: 'asc' - }, - select: { - id: true, - name: true, - extension: true, - idStorage: true - } - }) - - allData = dataFile - - } else if (kategori == "member") { - const dataMember = await prisma.projectMember.findMany({ - where: { - isActive: true, - idProject: String(id) - }, - select: { - id: true, - idUser: true, - User: { - select: { - name: true, - email: true, - img: true, - Position: { - select: { - name: true - } - } - } - }, - } - }) - - const fix = dataMember.map((v: any) => ({ - ..._.omit(v, ["User"]), - name: v.User.name, - email: v.User.email, - img: v.User.img, - position: v.User.Position.name - })) - - allData = fix - } else if (kategori == "link") { - const dataLink = await prisma.projectLink.findMany({ - where: { - isActive: true, - idProject: String(id) - }, - orderBy: { - createdAt: 'asc' - } - }) - allData = dataLink - } - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan kegiatan", data: allData }, { status: 200 }); - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan kegiatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/project/route.ts b/src/app/api/ai/project/route.ts deleted file mode 100644 index 7715ee5..0000000 --- a/src/app/api/ai/project/route.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { prisma } from "@/module/_global"; -import _, { ceil } from "lodash"; -import { NextResponse } from "next/server"; - - -// GET ALL DATA PROJECT -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const idVillage = searchParams.get('desa'); - const name = searchParams.get('search'); - const status = searchParams.get('status'); - const idGroup = searchParams.get("group"); - const page = searchParams.get('page'); - const get = searchParams.get('get'); - - let getFix = 10; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - - - - let kondisi: any = { - isActive: true, - idVillage: String(idVillage), - title: { - contains: (name == undefined || name == "null") ? "" : name, - mode: "insensitive" - }, - } - - if (status != "null" && status != undefined && status != "") { - kondisi = { - ...kondisi, - status: status == "segera" ? 0 : status == "dikerjakan" ? 1 : status == "selesai" ? 2 : status == "batal" ? 3 : 0 - } - } - - - if (idGroup != "null" && idGroup != undefined && idGroup != "") { - kondisi = { - ...kondisi, - idGroup: String(idGroup) - } - } - - - const data = await prisma.project.findMany({ - skip: dataSkip, - take: getFix, - where: kondisi, - select: { - id: true, - idGroup: true, - title: true, - desc: true, - status: true, - ProjectMember: { - where: { - isActive: true - }, - select: { - idUser: true - } - }, - ProjectTask: { - where: { - isActive: true - }, - select: { - title: true, - status: true - } - }, - Group: { - select: { - name: true - } - } - }, - orderBy: { - createdAt: 'desc' - } - }) - - const omitData = data.map((v: any) => ({ - ..._.omit(v, ["ProjectMember", "ProjectTask", "status", "Group"]), - group: v.Group.name, - status: v.status == 1 ? "dikerjakan" : v.status == 2 ? "selesai" : v.status == 3 ? "batal" : "segera", - progress: ceil((v.ProjectTask.filter((i: any) => i.status == 1).length * 100) / v.ProjectTask.length), - member: v.ProjectMember.length - })) - - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan kegiatan", data: omitData }, { status: 200 }); - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan kegiatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/task/[id]/route.ts b/src/app/api/ai/task/[id]/route.ts deleted file mode 100644 index 75b3261..0000000 --- a/src/app/api/ai/task/[id]/route.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import moment from "moment"; -import "moment/locale/id"; -import { NextResponse } from "next/server"; - - -// GET DETAIL TASK DIVISI / GET ONE -export async function GET(request: Request, context: { params: { id: string } }) { - try { - let allData - const { id } = context.params; - const { searchParams } = new URL(request.url); - const kategori = searchParams.get("cat"); - - const data = await prisma.divisionProject.findUnique({ - where: { - id: String(id), - }, - select: { - id: true, - idDivision: true, - title: true, - status: true, - desc: true, - reason: true, - report: true, - isActive: true, - Division: { - select: { - name: true - } - } - } - }); - - if (!data) { - return NextResponse.json({ success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", }, { status: 404 }); - } - - if (kategori == "data") { - const dataProgress = await prisma.divisionProjectTask.findMany({ - where: { - isActive: true, - idProject: String(id) - }, - orderBy: { - updatedAt: 'desc' - } - }) - - const semua = dataProgress.length - const selesai = _.filter(dataProgress, { status: 1 }).length - const progress = Math.ceil((selesai / semua) * 100) - - - allData = { - id: data.id, - idDivision: data.idDivision, - division: data.Division.name, - title: data.title, - status: data.status == 3 ? "batal" : data.status == 2 ? "selesai" : data.status == 1 ? "dikerjakan" : "segera", - desc: data.desc, - reason: data.reason, - report: data.report, - isActive: data.isActive, - progress: progress, - } - } else if (kategori == "task") { - const dataProgress = await prisma.divisionProjectTask.findMany({ - where: { - isActive: true, - idProject: String(id) - }, - select: { - id: true, - title: true, - status: true, - dateStart: true, - dateEnd: true, - }, - orderBy: { - createdAt: 'asc' - } - }) - - const fix = dataProgress.map((v: any) => ({ - ..._.omit(v, ["dateStart", "dateEnd", "status"]), - status: v.status == 1 ? "selesai" : "belum selesai", - dateStart: moment(v.dateStart).format("DD-MM-YYYY"), - dateEnd: moment(v.dateEnd).format("DD-MM-YYYY"), - })) - - allData = fix - - } else if (kategori == "file") { - const dataFile = await prisma.divisionProjectFile.findMany({ - where: { - isActive: true, - idProject: String(id) - }, - select: { - id: true, - ContainerFileDivision: { - select: { - id: true, - name: true, - extension: true, - idStorage: true - } - } - } - }) - - const fix = dataFile.map((v: any) => ({ - ..._.omit(v, ["ContainerFileDivision"]), - nameInStorage: v.ContainerFileDivision.id, - name: v.ContainerFileDivision.name, - extension: v.ContainerFileDivision.extension, - idStorage: v.ContainerFileDivision.idStorage, - })) - - allData = fix - - } else if (kategori == "member") { - const dataMember = await prisma.divisionProjectMember.findMany({ - where: { - isActive: true, - idProject: String(id) - }, - select: { - id: true, - idUser: true, - User: { - select: { - name: true, - email: true, - img: true, - Position: { - select: { - name: true - } - } - } - } - } - }) - - - const fix = dataMember.map((v: any) => ({ - ..._.omit(v, ["User"]), - name: v.User.name, - email: v.User.email, - img: v.User.img, - position: v.User.Position.name - })) - - allData = fix - } else if (kategori == "link") { - const dataLink = await prisma.divisionProjectLink.findMany({ - where: { - isActive: true, - idProject: String(id) - }, - orderBy: { - createdAt: 'asc' - } - }) - - allData = dataLink - } - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan tugas divisi", data: allData }, { status: 200 }); - - } - catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan tugas divisi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/task/route.ts b/src/app/api/ai/task/route.ts deleted file mode 100644 index 3fd53dc..0000000 --- a/src/app/api/ai/task/route.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { prisma } from "@/module/_global"; -import _, { ceil } from "lodash"; -import { NextResponse } from "next/server"; - - -// GET ALL DATA TUGAS DIVISI -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const villageId = searchParams.get('desa'); - const division = searchParams.get('division'); - const search = searchParams.get('search'); - const status = searchParams.get('status'); - const page = searchParams.get('page'); - const get = searchParams.get('get'); - - let getFix = 10; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - let kondisi: any = { - isActive: true, - Division: { - idVillage: String(villageId) - }, - title: { - contains: (search == undefined || search == "null") ? "" : search, - mode: "insensitive" - } - } - - if (status != "null" && status != undefined && status != "" && status != null) { - kondisi = { - ...kondisi, - status: status == "segera" ? 0 : status == "dikerjakan" ? 1 : status == "selesai" ? 2 : status == "batal" ? 3 : 0 - } - } - - if (division != "null" && division != undefined && division != "" && division != null) { - kondisi = { - ...kondisi, - idDivision: String(division) - } - } - - - const data = await prisma.divisionProject.findMany({ - skip: dataSkip, - take: getFix, - where: kondisi, - select: { - id: true, - idDivision: true, - title: true, - desc: true, - status: true, - DivisionProjectTask: { - where: { - isActive: true - }, - select: { - title: true, - status: true - } - }, - DivisionProjectMember: { - where: { - isActive: true - }, - select: { - idUser: true - } - }, - Division: { - select: { - name: true - } - } - }, - orderBy: { - createdAt: "desc" - } - }); - - const formatData = data.map((v: any) => ({ - ..._.omit(v, ["DivisionProjectTask", "DivisionProjectMember", "status", "Division"]), - division: v.Division.name, - status: v.status == 1 ? "dikerjakan" : v.status == 2 ? "selesai" : v.status == 3 ? "batal" : "segera", - progress: ceil((v.DivisionProjectTask.filter((i: any) => i.status == 1).length * 100) / v.DivisionProjectTask.length), - member: v.DivisionProjectMember.length, - })) - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: formatData }, { status: 200 }); - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/user/[id]/route.ts b/src/app/api/ai/user/[id]/route.ts deleted file mode 100644 index bb8048c..0000000 --- a/src/app/api/ai/user/[id]/route.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import { NextResponse } from "next/server"; - -// GET ONE MEMBER / USER -export async function GET(request: Request, context: { params: { id: string } }) { - try { - const { id } = context.params; - - const users = await prisma.user.findUnique({ - where: { - id: id, - }, - select: { - id: true, - nik: true, - name: true, - phone: true, - email: true, - gender: true, - img: true, - idGroup: true, - isActive: true, - idPosition: true, - createdAt: true, - updatedAt: true, - UserRole: { - select: { - name: true, - id: true - } - }, - Position: { - select: { - name: true, - id: true - }, - }, - Group: { - select: { - name: true, - id: true - }, - }, - }, - }); - - const { ...userData } = users; - const group = users?.Group.name - const position = users?.Position?.name - const idUserRole = users?.UserRole.id - const phone = '+62' + users?.phone - const role = users?.UserRole.name - const gender = users?.gender == "F" ? "Perempuan" : "Laki-Laki" - - const result = { ...userData, gender, group, position, idUserRole, phone, role }; - - const omitData = _.omit(result, ["Group", "Position", "UserRole"]); - - - - return NextResponse.json( - { - success: true, - message: "Berhasil mendapatkan anggota", - data: omitData, - }, - { status: 200 } - ); - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan anggota, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/user/route.ts b/src/app/api/ai/user/route.ts deleted file mode 100644 index 0f5908e..0000000 --- a/src/app/api/ai/user/route.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import { NextResponse } from "next/server"; - -// GET ALL MEMBER / USER -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const search = searchParams.get('search') - const idVillage = searchParams.get("desa"); - const idGroup = searchParams.get("group"); - const active = searchParams.get("active"); - const page = searchParams.get('page'); - const get = searchParams.get('get'); - - let getFix = 10; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - let kondisi: any = { - isActive: active == 'false' ? false : true, - idVillage: String(idVillage), - name: { - contains: (search == undefined || search == null) ? "" : search, - mode: "insensitive", - }, - NOT: { - idUserRole: 'developer' - } - } - - if (idGroup != "null" && idGroup != undefined && idGroup != "" && idGroup != null) { - kondisi = { - ...kondisi, - idGroup: String(idGroup) - } - } - - - const users = await prisma.user.findMany({ - skip: dataSkip, - take: getFix, - where: kondisi, - select: { - id: true, - idUserRole: true, - isActive: true, - nik: true, - name: true, - phone: true, - Position: { - select: { - name: true, - }, - }, - Group: { - select: { - name: true, - }, - }, - }, - orderBy: { - name: 'asc' - } - }); - - const allData = users.map((v: any) => ({ - ..._.omit(v, ["phone", "gender", "Group", "Position"]), - gender: v.gender == "F" ? "Perempuan" : "Laki-Laki", - phone: "+" + v.phone, - group: v.Group.name, - position: v?.Position?.name - })) - - return NextResponse.json({ success: true, message: "Berhasil member", data: allData }, { status: 200 }); - - - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan anggota, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/ai/village/route.ts b/src/app/api/ai/village/route.ts deleted file mode 100644 index 9377c22..0000000 --- a/src/app/api/ai/village/route.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { prisma } from "@/module/_global"; -import _ from "lodash"; -import { NextResponse } from "next/server"; - -export async function GET(request: Request) { - try { - const { searchParams } = new URL(request.url); - const isActive = searchParams.get("active"); - const search = searchParams.get('search'); - const page = searchParams.get('page') - const get = searchParams.get('get') - - let getFix = 10; - if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { - getFix = 10; - } else { - getFix = Number(get); - } - - const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; - - const data = await prisma.village.findMany({ - skip: dataSkip, - take: getFix, - where: { - isActive: isActive == 'false' ? false : true, - name: { - contains: (search == undefined || search == null) ? "" : search, - mode: "insensitive" - } - }, - select: { - id: true, - name: true, - isActive: true, - createdAt: true, - updatedAt: true - }, - orderBy: { - name: 'asc' - } - }); - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan desa", data, }, { status: 200 }); - - } catch (error) { - console.error(error); - return NextResponse.json({ success: false, message: "Gagal mendapatkan desa, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); - } -} \ No newline at end of file diff --git a/src/app/api/monitoring/[[...slug]]/route.ts b/src/app/api/monitoring/[[...slug]]/route.ts index c892c36..33dca7a 100644 --- a/src/app/api/monitoring/[[...slug]]/route.ts +++ b/src/app/api/monitoring/[[...slug]]/route.ts @@ -12,7 +12,7 @@ import "moment/locale/id"; const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) .use(cors({ origin: "*", - methods: ["GET", "POST", "OPTIONS"], + methods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization"] })) .use(swagger({ @@ -1531,11 +1531,79 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" }) }, } ) - ; -; + // ─── API KEY MANAGEMENT ────────────────────────────────────────────────── + .get("/api-keys", async ({ set }) => { + try { + const keys = await prisma.apiKey.findMany({ orderBy: { createdAt: "desc" } }); + return { + success: true, + message: "Berhasil mendapatkan API keys", + data: keys.map((k) => ({ ...k, key: k.key.slice(0, 8) + "••••••••" + k.key.slice(-4) })), + }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mendapatkan API keys" }; + } + }, { + detail: { summary: "List API Keys", tags: ["api-key"] }, + }) + + .post("/api-keys", async ({ body, set }) => { + try { + const rawKey = "ak_" + crypto.randomUUID().replace(/-/g, ""); + const key = await prisma.apiKey.create({ data: { name: body.name, key: rawKey } }); + return { + success: true, + message: "API key berhasil dibuat", + data: { ...key, key: rawKey }, + }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal membuat API key" }; + } + }, { + body: t.Object({ name: t.String({ description: "Nama key" }) }), + detail: { summary: "Buat API Key", tags: ["api-key"] }, + }) + + .patch("/api-keys/:id", async ({ params, body, set }) => { + try { + const key = await prisma.apiKey.update({ + where: { id: params.id }, + data: { isActive: body.isActive }, + }); + return { + success: true, + message: "API key berhasil diupdate", + data: { ...key, key: key.key.slice(0, 8) + "••••••••" + key.key.slice(-4) }, + }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal mengupdate API key" }; + } + }, { + params: t.Object({ id: t.String() }), + body: t.Object({ isActive: t.Boolean({ description: "Status aktif" }) }), + detail: { summary: "Toggle API Key", tags: ["api-key"] }, + }) + + .delete("/api-keys/:id", async ({ params, set }) => { + try { + await prisma.apiKey.delete({ where: { id: params.id } }); + return { success: true, message: "API key berhasil dihapus" }; + } catch (error) { + set.status = 500; + return { success: false, message: "Gagal menghapus API key" }; + } + }, { + params: t.Object({ id: t.String() }), + detail: { summary: "Hapus API Key", tags: ["api-key"] }, + }); export const GET = MonitoringServer.handle; export const POST = MonitoringServer.handle; +export const PATCH = MonitoringServer.handle; +export const DELETE = MonitoringServer.handle; export const OPTIONS = MonitoringServer.handle;