Files
jenna-mcp/src/server/routes/pengaduan_route.ts
2025-11-10 10:34:30 +08:00

633 lines
16 KiB
TypeScript

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