import Elysia, { t } from "elysia" import type { StatusPengaduan } from "generated/prisma" import { getLastUpdated } from "../lib/get-last-updated" import { generateNoPengaduan } from "../lib/no-pengaduan" import { normalizePhoneNumber } from "../lib/normalizePhone" import { prisma } from "../lib/prisma" import { defaultConfigSF, uploadFile, uploadFileBase64 } from "../lib/seafile" const PengaduanRoute = new Elysia({ prefix: "pengaduan", tags: ["pengaduan"], }) // --- KATEGORI PENGADUAN --- .get("/category", async () => { const data = await prisma.categoryPengaduan.findMany({ where: { isActive: true }, orderBy: { name: "asc" } }) return data }, { detail: { summary: "List Kategori Pengaduan", description: `tool untuk mendapatkan list kategori pengaduan`, tags: ["mcp"] } }) .post("/category/create", async ({ body }) => { const { name } = body await prisma.categoryPengaduan.create({ data: { name, } }) return ` ${JSON.stringify(body)} kategori pengaduan sudah dibuat` }, { body: t.Object({ name: t.String({ minLength: 1, error: "name harus diisi" }), }), detail: { summary: "buat kategori pengaduan", description: `tool untuk membuat kategori pengaduan` } }) .post("/category/update", async ({ body }) => { const { id, name } = body await prisma.categoryPengaduan.update({ where: { id, }, data: { name } }) return ` ${JSON.stringify(body)} kategori pengaduan sudah diperbarui` }, { body: t.Object({ id: t.String({ minLength: 1, error: "id harus diisi" }), name: t.String({ minLength: 1, error: "name harus diisi" }), }), detail: { summary: "update kategori pengaduan", description: `tool untuk update kategori pengaduan` } }) .post("/category/delete", async ({ body }) => { const { id } = body await prisma.categoryPengaduan.update({ where: { id, }, data: { isActive: false } }) return ` ${JSON.stringify(body)} kategori pengaduan sudah dihapus` }, { body: t.Object({ id: t.String({ minLength: 1, error: "id harus diisi" }), }), detail: { summary: "delete kategori pengaduan", description: `tool untuk delete kategori pengaduan` } }) // --- PENGADUAN --- .post("/create", async ({ body }) => { const { title, detail, location, image, idCategory, idWarga, phone } = body const noPengaduan = await generateNoPengaduan() let idCategoryFix = idCategory let idWargaFix = idWarga const category = await prisma.categoryPengaduan.findUnique({ where: { id: idCategory, } }) if (!category) { const cariCategory = await prisma.categoryPengaduan.findFirst({ where: { name: idCategory, } }) if (!cariCategory) { idCategoryFix = "lainnya" } else { idCategoryFix = cariCategory.id } } const warga = await prisma.warga.findUnique({ where: { id: idWarga, } }) if (!warga) { const nomorHP = normalizePhoneNumber({ phone }) const cariWarga = await prisma.warga.findUnique({ where: { phone: nomorHP, } }) if (!cariWarga) { const wargaCreate = await prisma.warga.create({ data: { name: idWarga, phone: nomorHP, }, select: { id: true } }) idWargaFix = wargaCreate.id } else { idWargaFix = cariWarga.id } } const pengaduan = await prisma.pengaduan.create({ data: { title, detail, idCategory: idCategoryFix, idWarga: idWargaFix, location, image, noPengaduan, }, select: { id: true, } }) if (!pengaduan.id) { throw new Error("gagal membuat pengaduan") } await prisma.historyPengaduan.create({ data: { idPengaduan: pengaduan.id, deskripsi: "Pengaduan dibuat", } }) return ` ${JSON.stringify(body)} pengaduan sudah dibuat` }, { body: t.Object({ title: t.String({ minLength: 1, error: "title harus diisi" }), detail: t.String({ minLength: 1, error: "detail harus diisi" }), location: t.String({ minLength: 1, error: "location harus diisi" }), image: t.Any(), idCategory: t.String({ minLength: 1, error: "idCategory harus diisi" }), idWarga: t.String({ minLength: 1, error: "idWarga harus diisi" }), phone: t.String({ minLength: 1, error: "phone harus diisi" }), }), detail: { summary: "Create Pengaduan Warga", description: `tool untuk membuat pengaduan warga`, tags: ["mcp"] } }) .post("/update-status", async ({ body }) => { const { id, status, keterangan, idUser } = body let deskripsi = "" const pengaduan = await prisma.pengaduan.update({ where: { id, }, data: { status: status as StatusPengaduan, keterangan, } }) if (!pengaduan) { throw new Error("gagal membuat pengaduan") } if (status === "diterima") { deskripsi = "Pengaduan diterima oleh admin" } else if (status === "dikerjakan") { deskripsi = "Pengaduan dikerjakan oleh petugas" } else if (status === "ditolak") { deskripsi = "Pengaduan ditolak dengan keterangan " + keterangan } else if (status === "selesai") { deskripsi = "Pengaduan selesai" } await prisma.historyPengaduan.create({ data: { idPengaduan: pengaduan.id, deskripsi, status: status as StatusPengaduan, idUser, } }) return ` ${JSON.stringify(body)} status pengaduan sudah diupdate` }, { body: t.Object({ id: t.String({ minLength: 1, error: "id harus diisi" }), status: t.String({ minLength: 1, error: "status harus diisi" }), keterangan: t.Any(), idUser: t.String({ minLength: 1, error: "idUser harus diisi" }), }), detail: { summary: "Update status pengaduan", description: `tool untuk update status pengaduan` } }) .get("/detail", async ({ query }) => { const { id } = query const data = await prisma.pengaduan.findUnique({ where: { id, }, select: { id: true, noPengaduan: true, title: true, detail: true, location: true, image: true, idCategory: true, idWarga: true, status: true, keterangan: true, createdAt: true, updatedAt: true, CategoryPengaduan: { select: { name: true } }, Warga: { select: { name: true, } } } }) const dataHistory = await prisma.historyPengaduan.findMany({ where: { idPengaduan: id, }, select: { id: true, deskripsi: true, status: true, createdAt: true, idUser: true, User: { select: { name: true, } } } }) const dataHistoryFix = dataHistory.map((item) => { return { id: item.id, deskripsi: item.deskripsi, status: item.status, createdAt: item.createdAt, idUser: item.idUser, nameUser: item.User?.name, } }) const datafix = { id: data?.id, noPengaduan: data?.noPengaduan, title: data?.title, detail: data?.detail, location: data?.location, image: data?.image, CategoryPengaduan: data?.CategoryPengaduan.name, idWarga: data?.idWarga, nameWarga: data?.Warga?.name, status: data?.status, keterangan: data?.keterangan, createdAt: data?.createdAt, updatedAt: data?.updatedAt, history: dataHistoryFix, } return datafix }, { detail: { summary: "Detail Pengaduan Warga", description: `tool untuk mendapatkan detail pengaduan warga / history pengaduan / mengecek status pengaduan`, tags: ["mcp"] } }) .get("/", async ({ query }) => { const { take, page, search, phone } = query const skip = !page ? 0 : (Number(page) - 1) * (!take ? 10 : Number(take)) const data = await prisma.pengaduan.findMany({ skip, take: !take ? 10 : Number(take), orderBy: { createdAt: "asc" }, where: { isActive: true, OR: [ { title: { contains: search ?? "", mode: "insensitive" }, }, { noPengaduan: { contains: search ?? "", mode: "insensitive" }, }, { detail: { contains: search ?? "", mode: "insensitive" }, } ], AND: { Warga: { phone: phone } } }, select: { id: true, noPengaduan: true, title: true, detail: true, location: true, status: true, createdAt: true, CategoryPengaduan: { select: { name: true } }, Warga: { select: { name: true, } } } }) const dataFix = data.map((item) => { return { noPengaduan: item.noPengaduan, title: item.title, detail: item.detail, status: item.status, createdAt: item.createdAt.toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" }), } }) return dataFix }, { query: t.Object({ take: t.String({ optional: true }), page: t.String({ optional: true }), search: t.String({ optional: true }), phone: t.String({ minLength: 11, error: "phone harus diisi" }), }), detail: { summary: "List Pengaduan Warga By Phone", description: `tool untuk mendapatkan list pengaduan warga by phone`, tags: ["mcp"] } }) .post("/upload", async ({ body }) => { const { file } = body; // Validasi file if (!file) { return { success: false, message: "File tidak ditemukan" }; } // Upload ke Seafile (pastikan uploadFile menerima Blob atau ArrayBuffer) // const buffer = await file.arrayBuffer(); const result = await uploadFile(defaultConfigSF, file); return { success: true, message: "Upload berhasil", filename: file.name, size: file.size, seafileResult: result }; }, { body: t.Object({ file: t.File({ format: "binary" }) }), detail: { summary: "Upload File", description: "Tool untuk upload file ke Seafile", tags: ["mcp"], consumes: ["multipart/form-data"] }, }) .post("/upload-base64", async ({ body }) => { const { file } = body; // Validasi file if (!file) { return { success: false, message: "File tidak ditemukan" }; } // Konversi file ke base64 // const buffer = await file.arrayBuffer(); // const base64String = Buffer.from(buffer).toString("base64"); // (Opsional) jika perlu dikirim ke Seafile sebagai base64 const result = await uploadFileBase64(defaultConfigSF, { name: 'contoh', data: file }); return { success: true, message: "Upload berhasil", // filename: file.name, // size: file.size, // base64Preview: base64String.slice(0, 100) + "...", // hanya preview seafileResult: result }; }, { body: t.Object({ file: t.String() }), detail: { summary: "Upload File (Base64)", description: "Tool untuk upload file ke Seafile dalam format Base64", tags: ["mcp"], consumes: ["multipart/form-data"] }, }) .get("/list", async ({ query }) => { const { take, page, search, status } = query const skip = !page ? 0 : (Number(page) - 1) * (!take ? 10 : Number(take)) let where: any = { isActive: true, OR: [ { title: { contains: search ?? "", mode: "insensitive" }, }, { noPengaduan: { contains: search ?? "", mode: "insensitive" }, }, { detail: { contains: search ?? "", mode: "insensitive" }, }, { Warga: { phone: { contains: search ?? "", mode: "insensitive" }, }, } ] } if (status && status !== "semua") { where = { ...where, status: status } } const data = await prisma.pengaduan.findMany({ skip, take: !take ? 10 : Number(take), orderBy: { createdAt: "desc" }, where, select: { id: true, noPengaduan: true, title: true, detail: true, location: true, status: true, createdAt: true, updatedAt: true, CategoryPengaduan: { select: { name: true } }, Warga: { select: { name: true, } } } }) const dataFix = data.map((item) => { return { noPengaduan: item.noPengaduan, title: item.title, detail: item.detail, status: item.status, location: item.location, createdAt: item.createdAt.toLocaleDateString("id-ID", { day: "numeric", month: "long", year: "numeric" }), updatedAt: 'terakhir diperbarui ' + getLastUpdated(item.updatedAt), } }) return dataFix }, { query: t.Object({ take: t.String({ optional: true }), page: t.String({ optional: true }), search: t.String({ optional: true }), status: t.String({ optional: true }), }), detail: { summary: "List Pengaduan Warga", description: `tool untuk mendapatkan list pengaduan warga`, } }) .get("/count", async ({ query }) => { const counts = await prisma.pengaduan.groupBy({ by: ['status'], where: { isActive: true, }, _count: { status: true, }, }); const grouped = Object.fromEntries( counts.map(c => [c.status, c._count.status]) ); const total = await prisma.pengaduan.count({ where: { isActive: true }, }); return { antrian: grouped?.antrian || 0, diterima: grouped?.diterima || 0, dikerjakan: grouped?.dikerjakan || 0, ditolak: grouped?.ditolak || 0, selesai: grouped?.selesai || 0, semua: total, }; }, { detail: { summary: "Jumlah Pengaduan Warga", description: `tool untuk mendapatkan jumlah pengaduan warga`, } }) ; export default PengaduanRoute