Compare commits

...

3 Commits

Author SHA1 Message Date
06feeae9a5 upd: api pengaduan
Deskripsi:
- update seeder kategori pengaduan
- list pengaduan warga

NO Issues
2025-10-29 14:41:40 +08:00
b102643675 Merge pull request 'upd: database' (#3) from amalia/28-okt-25-v2 into main
Reviewed-on: http://wibugit.wibudev.com/wibu/jenna-mcp/pulls/3
2025-10-28 17:29:27 +08:00
bipproduction
b2f8dc3714 tambahan 2025-10-28 17:28:18 +08:00
5 changed files with 191 additions and 25 deletions

View File

@@ -1,5 +1,28 @@
import { prisma } from "@/server/lib/prisma";
const category = [
{
id: "lainnya",
name: "Lainnya"
},
{
id: "kebersihan",
name: "Kebersihan"
},
{
id: "keamanan",
name: "Keamanan"
},
{
id: "pelayanan",
name: "Pelayanan"
},
{
id: "infrastruktur",
name: "Infrastruktur"
},
]
const role = [
{
id: "developer",
@@ -36,7 +59,7 @@ const user = [
console.log(`✅ Role ${r.name} seeded successfully`)
}
for (const u of user) {
await prisma.user.upsert({
where: { email: u.email },
@@ -47,7 +70,17 @@ const user = [
console.log(`✅ User ${u.email} seeded successfully`)
}
for (const c of category) {
await prisma.categoryPengaduan.upsert({
where: { id: c.id },
create: c,
update: c
})
console.log(`✅ Category ${c.name} seeded successfully`)
}
})().catch((e) => {

View File

@@ -17,7 +17,7 @@ interface McpTool {
/**
* Convert OpenAPI 3.x JSON spec into MCP-compatible tool definitions (without run()).
* Each tool corresponds to an endpoint, with metadata stored under `x-props`.
* Hanya menyertakan endpoint yang memiliki tag berisi "mcp".
*/
export function convertOpenApiToMcpTools(openApiJson: any): McpTool[] {
const tools: McpTool[] = [];
@@ -28,10 +28,14 @@ export function convertOpenApiToMcpTools(openApiJson: any): McpTool[] {
if (path.startsWith("/mcp")) continue;
for (const [method, operation] of Object.entries<any>(methods as any)) {
const tags: string[] = Array.isArray(operation.tags) ? operation.tags : [];
// ✅ exclude semua yang tidak punya tag atau tag-nya tidak mengandung "mcp"
if (!tags.length || !tags.some(t => t.toLowerCase().includes("mcp"))) continue;
const rawName = _.snakeCase(operation.operationId || `${method}_${path}`) || "unnamed_tool";
const name = cleanToolName(rawName);
const summary = operation.summary || `Execute ${method.toUpperCase()} ${path}`;
const description =
operation.description ||
operation.summary ||
@@ -51,9 +55,9 @@ export function convertOpenApiToMcpTools(openApiJson: any): McpTool[] {
method: method.toUpperCase(),
path,
operationId: operation.operationId,
tag: Array.isArray(operation.tags) ? operation.tags[0] : undefined,
tag: tags[0],
deprecated: operation.deprecated || false,
summary: operation.summary, // ✅ tambahkan summary ke metadata
summary: operation.summary,
},
inputSchema: {
...schema,
@@ -87,19 +91,18 @@ function cleanToolName(name: string): string {
.replace(/(^_|_$)/g, "");
}
// === Contoh Pemakaian ===
// import openApiJson from "./openapi.json";
// const tools = convertOpenApiToMcpTools(openApiJson, "https://api.wibudev.com");
// console.log(JSON.stringify(tools, null, 2));
export async function getMcpTools(){
/**
* Ambil OpenAPI JSON dari endpoint dan konversi ke tools MCP
*/
export async function getMcpTools() {
const data = await fetch(`${process.env.BUN_PUBLIC_BASE_URL}/docs/json`);
const openApiJson = await data.json();
const tools = convertOpenApiToMcpTools(openApiJson);
return tools;
}
// === CLI Mode ===
if (import.meta.main) {
const tools = await getMcpTools();
Bun.write("./tools.json", JSON.stringify(tools, null, 2));
await Bun.write("./tools.json", JSON.stringify(tools, null, 2));
}

View File

@@ -96,6 +96,7 @@ const LayananRoute = new Elysia({
detail: {
summary: "Create Layanan KTP/KK",
description: "Create a new service request for KTP or KK.",
tags: ["mcp"],
},
}
)
@@ -131,6 +132,7 @@ const LayananRoute = new Elysia({
detail: {
summary: "Cek Status KTP",
description: "Retrieve the current status of a KTP/KK request by unique ID.",
tags: ["mcp"],
},
}
);

View File

@@ -151,7 +151,7 @@ async function handleMCPRequestAsync(
// Elysia MCP Server
// =====================
export const MCPRoute = new Elysia({
tags: ["MCP"]
tags: ["MCP Server"]
})
.post("/mcp", async ({ request, set }) => {
if (!tools.length) {

View File

@@ -18,8 +18,9 @@ const PengaduanRoute = new Elysia({
return data
}, {
detail: {
summary: "get kategori pengaduan",
description: `tool untuk mendapatkan kategori pengaduan`
summary: "List Kategori Pengaduan",
description: `tool untuk mendapatkan list kategori pengaduan`,
tags: ["mcp"]
}
})
.post("/category/create", async ({ body }) => {
@@ -100,15 +101,67 @@ const PengaduanRoute = new Elysia({
// --- PENGADUAN ---
.post("/create", async ({ body }) => {
const { title, detail, location, image, idCategory, idWarga } = 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 cariWarga = await prisma.warga.findFirst({
where: {
phone,
}
})
if (!cariWarga) {
const wargaCreate = await prisma.warga.create({
data: {
name: idWarga,
phone,
},
select: {
id: true
}
})
idWargaFix = wargaCreate.id
} else {
idWargaFix = cariWarga.id
}
}
const pengaduan = await prisma.pengaduan.create({
data: {
title,
detail,
idCategory,
idWarga,
idCategory: idCategoryFix,
idWarga: idWargaFix,
location,
image,
noPengaduan,
@@ -138,13 +191,15 @@ const PengaduanRoute = new Elysia({
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.String({ minLength: 1, error: "image 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: "buat pengaduan",
description: `tool untuk membuat pengaduan`
summary: "Create Pengaduan Warga",
description: `tool untuk membuat pengaduan warga`,
tags: ["mcp"]
}
})
.post("/update-status", async ({ body }) => {
@@ -197,7 +252,7 @@ const PengaduanRoute = new Elysia({
}),
detail: {
summary: "update status pengaduan",
summary: "Update status pengaduan",
description: `tool untuk update status pengaduan`
}
})
@@ -282,8 +337,81 @@ const PengaduanRoute = new Elysia({
return datafix
}, {
detail: {
summary: "get detail pengaduan",
description: `tool untuk mendapatkan detail pengaduan`
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 } = 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"
},
}
],
},
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
}, {
detail: {
summary: "List Pengaduan Warga",
description: `tool untuk mendapatkan list pengaduan warga`,
tags: ["mcp"]
}
})
export default PengaduanRoute