Compare commits
23 Commits
amalia/02-
...
amalia/09-
| Author | SHA1 | Date | |
|---|---|---|---|
| 6428f5084e | |||
| bfc292ec6c | |||
| 5680466c98 | |||
| f5cc45937c | |||
| 5b4164b151 | |||
| 225c58b346 | |||
| b8b3aed86e | |||
| fc530399dd | |||
| 281e34ea69 | |||
| f928fc504f | |||
| 4fb98d0480 | |||
| bfb33e2105 | |||
| 2579714000 | |||
| d69189cf7d | |||
| 20e24a03aa | |||
| c256f4b729 | |||
| 9430ad3728 | |||
| c6c3ba95f8 | |||
|
|
3c58230c3a | ||
| d22b4b973f | |||
| 9b7a61e134 | |||
| 7c669f3494 | |||
| b9984c6337 |
@@ -1,6 +1,6 @@
|
||||
export function isValidPhone(number: string): boolean {
|
||||
const clean = number.replace(/[\s.-]/g, ""); // hapus spasi, titik, strip
|
||||
const regex = /^(?:\+628|08)(\d{7,12})$/;
|
||||
const regex = /^(?:\+62|62|0)8\d{7,12}$/;
|
||||
return regex.test(clean);
|
||||
}
|
||||
|
||||
|
||||
@@ -128,7 +128,8 @@ function convertToMcpContent(payload: any) {
|
||||
export async function executeTool(
|
||||
tool: any,
|
||||
args: Record<string, any> = {},
|
||||
baseUrl: string
|
||||
baseUrl: string,
|
||||
xPayload: Record<string, any> = {}
|
||||
) {
|
||||
const x = tool["x-props"] || {};
|
||||
const method = (x.method || "GET").toUpperCase();
|
||||
@@ -247,6 +248,9 @@ export async function executeTool(
|
||||
|
||||
// Execute fetch
|
||||
console.log(`[MCP] → ${method} ${url}`);
|
||||
for(const [key, value] of Object.entries(xPayload)) {
|
||||
opts.headers![key] = value;
|
||||
}
|
||||
const res = await fetch(url, opts);
|
||||
|
||||
const resContentType = (res.headers.get("content-type") || "").toLowerCase();
|
||||
@@ -281,7 +285,7 @@ export async function executeTool(
|
||||
/* -------------------------
|
||||
JSON-RPC Handler
|
||||
------------------------- */
|
||||
async function handleMCPRequestAsync(request: JSONRPCRequest): Promise<JSONRPCResponse> {
|
||||
async function handleMCPRequestAsync(request: JSONRPCRequest, xPayload: Record<string, any>): Promise<JSONRPCResponse> {
|
||||
const { id, method, params } = request;
|
||||
|
||||
const makeError = (code: number, message: string, data?: any): JSONRPCResponse => ({
|
||||
@@ -331,7 +335,7 @@ async function handleMCPRequestAsync(request: JSONRPCRequest): Promise<JSONRPCRe
|
||||
const baseUrl = (params?.credentials?.baseUrl as string) || process.env.BUN_PUBLIC_BASE_URL || "http://localhost:3000";
|
||||
const args = params?.arguments || {};
|
||||
|
||||
const result = await executeTool(tool, args, baseUrl);
|
||||
const result = await executeTool(tool, args, baseUrl, xPayload);
|
||||
|
||||
// Extract the meaningful payload (prefer nested .data if present)
|
||||
const raw = extractRaw(result.data);
|
||||
@@ -365,7 +369,7 @@ async function handleMCPRequestAsync(request: JSONRPCRequest): Promise<JSONRPCRe
|
||||
Elysia App & Routes
|
||||
------------------------- */
|
||||
export const MCPRoute = new Elysia({ tags: ["MCP Server"] })
|
||||
.post("/mcp", async ({ request, set }) => {
|
||||
.post("/mcp", async ({ request, set, headers }) => {
|
||||
set.headers["Content-Type"] = "application/json";
|
||||
set.headers["Access-Control-Allow-Origin"] = "*";
|
||||
|
||||
@@ -378,12 +382,17 @@ export const MCPRoute = new Elysia({ tags: ["MCP Server"] })
|
||||
}
|
||||
}
|
||||
|
||||
const xPayload = {
|
||||
['x-user']: headers['x-user'] || "",
|
||||
['x-phone']: headers['x-phone'] || ""
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
|
||||
// If batch array -> allSettled for resilience
|
||||
if (Array.isArray(body)) {
|
||||
const promises = body.map((req: JSONRPCRequest) => handleMCPRequestAsync(req));
|
||||
const promises = body.map((req: JSONRPCRequest) => handleMCPRequestAsync(req, xPayload));
|
||||
const settled = await Promise.allSettled(promises);
|
||||
const responses = settled.map((s) =>
|
||||
s.status === "fulfilled"
|
||||
@@ -401,7 +410,7 @@ export const MCPRoute = new Elysia({ tags: ["MCP Server"] })
|
||||
return responses;
|
||||
}
|
||||
|
||||
const single = await handleMCPRequestAsync(body as JSONRPCRequest);
|
||||
const single = await handleMCPRequestAsync(body as JSONRPCRequest, xPayload);
|
||||
return single;
|
||||
} catch (err: any) {
|
||||
set.status = 400;
|
||||
|
||||
@@ -291,11 +291,14 @@ const PelayananRoute = new Elysia({
|
||||
tags: ["mcp"]
|
||||
}
|
||||
})
|
||||
.post("/create", async ({ body }) => {
|
||||
const { kategoriId, namaWarga, noTelepon, dataText, syaratDokumen } = body
|
||||
.post("/create", async ({ body, headers }) => {
|
||||
const { kategoriId, dataText, syaratDokumen } = body
|
||||
const namaWarga = headers['x-user'] || ""
|
||||
const noTelepon = headers['x-phone'] || ""
|
||||
const noPengajuan = await generateNoPengajuanSurat()
|
||||
let idCategoryFix = kategoriId
|
||||
let idWargaFix = ""
|
||||
|
||||
const category = await prisma.categoryPelayanan.findUnique({
|
||||
where: {
|
||||
id: kategoriId,
|
||||
@@ -359,6 +362,10 @@ const PelayananRoute = new Elysia({
|
||||
let dataInsertDataText = []
|
||||
|
||||
for (const item of syaratDokumen) {
|
||||
console.log('syarat dokumen', item)
|
||||
const jenisFix = (category?.syaratDokumen as Array<{ name: string; desc: string }>)
|
||||
?.find((v) => v.name === item.jenis || v.desc === item.jenis);
|
||||
console.log('jenis fix', jenisFix)
|
||||
dataInsertSyaratDokumen.push({
|
||||
idPengajuanLayanan: pengaduan.id,
|
||||
idCategory: idCategoryFix,
|
||||
@@ -368,6 +375,9 @@ const PelayananRoute = new Elysia({
|
||||
}
|
||||
|
||||
for (const item of dataText) {
|
||||
console.log('dataitem', item)
|
||||
const jenisFix = category?.dataText.find((v) => v.toLowerCase() == item.jenis.toLowerCase())
|
||||
console.log('data text fix', jenisFix)
|
||||
dataInsertDataText.push({
|
||||
idPengajuanLayanan: pengaduan.id,
|
||||
idCategory: idCategoryFix,
|
||||
@@ -376,6 +386,9 @@ const PelayananRoute = new Elysia({
|
||||
})
|
||||
}
|
||||
|
||||
console.log('datainsertsyaratdokumen', dataInsertSyaratDokumen)
|
||||
console.log('datainsertdatatext', dataInsertDataText)
|
||||
|
||||
await prisma.syaratDokumenPelayanan.createMany({
|
||||
data: dataInsertSyaratDokumen,
|
||||
})
|
||||
@@ -400,17 +413,17 @@ const PelayananRoute = new Elysia({
|
||||
examples: ["skusaha"],
|
||||
error: "ID kategori harus diisi"
|
||||
}),
|
||||
namaWarga: t.String({
|
||||
description: "Nama warga",
|
||||
examples: ["Budi Santoso"],
|
||||
error: "Nama warga harus diisi"
|
||||
}),
|
||||
// namaWarga: t.String({
|
||||
// description: "Nama warga",
|
||||
// examples: ["Budi Santoso"],
|
||||
// error: "Nama warga harus diisi"
|
||||
// }),
|
||||
|
||||
noTelepon: t.String({
|
||||
error: "Nomor telepon harus diisi",
|
||||
examples: ["08123456789", "+628123456789"],
|
||||
description: "Nomor telepon warga pelapor"
|
||||
}),
|
||||
// noTelepon: t.String({
|
||||
// error: "Nomor telepon harus diisi",
|
||||
// examples: ["08123456789", "+628123456789"],
|
||||
// description: "Nomor telepon warga pelapor"
|
||||
// }),
|
||||
|
||||
dataText: t.Array(
|
||||
t.Object({
|
||||
|
||||
@@ -107,8 +107,10 @@ const PengaduanRoute = new Elysia({
|
||||
|
||||
|
||||
// --- PENGADUAN ---
|
||||
.post("/create", async ({ body }) => {
|
||||
const { judulPengaduan, detailPengaduan, lokasi, namaGambar, kategoriId, namaWarga, noTelepon } = body
|
||||
.post("/create", async ({ body, headers }) => {
|
||||
const { judulPengaduan, detailPengaduan, lokasi, namaGambar, kategoriId } = body
|
||||
const namaWarga = headers['x-user'] || ""
|
||||
const noTelepon = headers['x-phone'] || ""
|
||||
let imageFix = namaGambar
|
||||
const noPengaduan = await generateNoPengaduan()
|
||||
let idCategoryFix = kategoriId
|
||||
@@ -140,7 +142,7 @@ const PengaduanRoute = new Elysia({
|
||||
}
|
||||
|
||||
if (!isValidPhone(noTelepon)) {
|
||||
return { success: false, message: 'nomor telepon tidak valid, harap masukkan nomor yang benar' }
|
||||
return { success: false, message: `nomor telepon ${noTelepon} tidak valid, harap masukkan nomor yang benar` }
|
||||
}
|
||||
|
||||
const nomorHP = normalizePhoneNumber({ phone: noTelepon })
|
||||
@@ -220,16 +222,16 @@ const PengaduanRoute = new Elysia({
|
||||
description: "ID atau nama kategori pengaduan (contoh: kebersihan, keamanan, lainnya)"
|
||||
})),
|
||||
|
||||
namaWarga: t.String({
|
||||
examples: ["budiman"],
|
||||
description: "Nama warga yang melapor"
|
||||
}),
|
||||
// namaWarga: t.String({
|
||||
// examples: ["budiman"],
|
||||
// description: "Nama warga yang melapor"
|
||||
// }),
|
||||
|
||||
noTelepon: t.String({
|
||||
error: "Nomor telepon harus diisi",
|
||||
examples: ["08123456789", "+628123456789"],
|
||||
description: "Nomor telepon warga pelapor"
|
||||
}),
|
||||
// noTelepon: t.String({
|
||||
// error: "Nomor telepon harus diisi",
|
||||
// examples: ["08123456789", "+628123456789"],
|
||||
// description: "Nomor telepon warga pelapor"
|
||||
// }),
|
||||
}),
|
||||
|
||||
detail: {
|
||||
@@ -289,6 +291,84 @@ const PengaduanRoute = new Elysia({
|
||||
description: `tool untuk update status pengaduan`
|
||||
}
|
||||
})
|
||||
.post("/update", async ({ body }) => {
|
||||
const { noPengaduan, judul, detail, lokasi, namaGambar } = body
|
||||
let dataUpdate = {}
|
||||
|
||||
const cek = await prisma.pengaduan.findFirst({
|
||||
where: {
|
||||
noPengaduan,
|
||||
},
|
||||
select: {
|
||||
id: true
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
if (!cek) {
|
||||
return { success: false, message: 'gagal update status pengaduan, nomer ' + noPengaduan + ' tidak ditemukan' }
|
||||
}
|
||||
|
||||
if (judul) {
|
||||
dataUpdate = { title: judul }
|
||||
}
|
||||
|
||||
if (detail) {
|
||||
dataUpdate = { ...dataUpdate, detail }
|
||||
}
|
||||
|
||||
if (lokasi) {
|
||||
dataUpdate = { ...dataUpdate, location: lokasi }
|
||||
}
|
||||
|
||||
if (namaGambar) {
|
||||
dataUpdate = { ...dataUpdate, image: namaGambar }
|
||||
}
|
||||
|
||||
const pengaduan = await prisma.pengaduan.updateMany({
|
||||
where: {
|
||||
noPengaduan
|
||||
},
|
||||
data: dataUpdate
|
||||
})
|
||||
|
||||
const keys = Object.keys(dataUpdate).join(", ");
|
||||
|
||||
await prisma.historyPengaduan.create({
|
||||
data: {
|
||||
idPengaduan: cek.id,
|
||||
deskripsi: `Pengaduan diupdate oleh warga (data yg diupdate: ${keys})`,
|
||||
}
|
||||
})
|
||||
|
||||
return { success: true, message: 'pengaduan dengan nomer ' + noPengaduan + ' sudah diupdate' }
|
||||
}, {
|
||||
body: t.Object({
|
||||
noPengaduan: t.String({
|
||||
error: "nomer pengaduan harus diisi",
|
||||
description: "Nomer pengaduan yang ingin diupdate"
|
||||
}),
|
||||
judul: t.Optional(t.String({
|
||||
error: "judul harus diisi",
|
||||
description: "Judul pengaduan yang ingin diupdate"
|
||||
})),
|
||||
detail: t.Optional(t.String({
|
||||
description: "detail pengaduan yang ingin diupdate"
|
||||
})),
|
||||
lokasi: t.Optional(t.String({
|
||||
description: "lokasi pengaduan yang ingin diupdate"
|
||||
})),
|
||||
namaGambar: t.Optional(t.String({
|
||||
description: "Nama file gambar yang telah diupload untuk update data pengaduan"
|
||||
})),
|
||||
}),
|
||||
|
||||
detail: {
|
||||
summary: "Update Data Pengaduan",
|
||||
description: `tool untuk update data pengaduan`,
|
||||
tags: ["mcp"]
|
||||
}
|
||||
})
|
||||
.get("/detail", async ({ query }) => {
|
||||
const { id } = query
|
||||
|
||||
|
||||
Reference in New Issue
Block a user