tasks/noc-integration/setup-skills-and-notifications/20260330-1610

This commit is contained in:
2026-03-30 16:58:30 +08:00
parent 2c5fa52608
commit 1b1dc71225
7 changed files with 93 additions and 37 deletions

View File

@@ -1,49 +1,52 @@
#!/usr/bin/env bun #!/usr/bin/env bun
import { readFileSync } from "node:fs"; import { readFileSync, existsSync } from "node:fs";
import { join } from "node:path";
// Fungsi untuk mencari string terpanjang dalam objek (biasanya balasan AI) // Function to manually load .env from project root if process.env is missing keys
function findLongestString(obj: unknown): string { function loadEnv() {
let longest = ""; const envPath = join(process.cwd(), ".env");
const search = (item: unknown) => { if (existsSync(envPath)) {
if (typeof item === "string") { const envContent = readFileSync(envPath, "utf-8");
if (item.length > longest.length) { const lines = envContent.split("\n");
longest = item; for (const line of lines) {
} if (line && !line.startsWith("#")) {
} else if (Array.isArray(item)) { const [key, ...valueParts] = line.split("=");
for (const child of item) { if (key && valueParts.length > 0) {
search(child); const value = valueParts.join("=").trim().replace(/^["']|["']$/g, "");
} process.env[key.trim()] = value;
} else if (item !== null && typeof item === "object") { }
for (const value of Object.values(item)) { }
search(value);
} }
} }
};
search(obj);
return longest;
} }
async function run() { async function run() {
try { try {
// Ensure environment variables are loaded
loadEnv();
const inputRaw = readFileSync(0, "utf-8"); const inputRaw = readFileSync(0, "utf-8");
if (!inputRaw) return; if (!inputRaw) return;
const input = JSON.parse(inputRaw);
// DEBUG: Lihat struktur asli di console terminal (stderr) let finalText = "";
console.error("DEBUG KEYS:", Object.keys(input)); let sessionId = "dashboard-desa-plus";
try {
// Try parsing as JSON first
const input = JSON.parse(inputRaw);
sessionId = input.session_id || "dashboard-desa-plus";
finalText = typeof input === "string" ? input : (input.response || input.text || JSON.stringify(input));
} catch {
// If not JSON, use raw text
finalText = inputRaw;
}
const BOT_TOKEN = process.env.BOT_TOKEN; const BOT_TOKEN = process.env.BOT_TOKEN;
const CHAT_ID = process.env.CHAT_ID; const CHAT_ID = process.env.CHAT_ID;
const sessionId = input.session_id || "unknown"; if (!BOT_TOKEN || !CHAT_ID) {
console.error("Missing BOT_TOKEN or CHAT_ID in environment variables");
// Cari teks secara otomatis di seluruh objek JSON return;
let finalText = findLongestString(input.response || input);
if (!finalText || finalText.length < 5) {
finalText =
"Teks masih gagal diekstraksi. Struktur: " +
Object.keys(input).join(", ");
} }
const message = const message =
@@ -51,7 +54,7 @@ async function run() {
`🆔 Session: \`${sessionId}\` \n\n` + `🆔 Session: \`${sessionId}\` \n\n` +
`🧠 Output:\n${finalText.substring(0, 3500)}`; `🧠 Output:\n${finalText.substring(0, 3500)}`;
await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, { const res = await fetch(`https://api.telegram.org/bot${BOT_TOKEN}/sendMessage`, {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ body: JSON.stringify({
@@ -61,6 +64,13 @@ async function run() {
}), }),
}); });
if (!res.ok) {
const errorData = await res.json();
console.error("Telegram API Error:", errorData);
} else {
console.log("Notification sent successfully!");
}
process.stdout.write(JSON.stringify({ status: "continue" })); process.stdout.write(JSON.stringify({ status: "continue" }));
} catch (err) { } catch (err) {
console.error("Hook Error:", err); console.error("Hook Error:", err);

View File

@@ -0,0 +1,15 @@
-- AlterTable
ALTER TABLE "activity" ALTER COLUMN "villageId" SET DEFAULT 'desa1';
-- AlterTable
ALTER TABLE "discussion" ALTER COLUMN "villageId" SET DEFAULT 'desa1';
-- AlterTable
ALTER TABLE "division" ADD COLUMN "externalActivityCount" INTEGER NOT NULL DEFAULT 0,
ALTER COLUMN "villageId" SET DEFAULT 'desa1';
-- AlterTable
ALTER TABLE "document" ALTER COLUMN "villageId" SET DEFAULT 'desa1';
-- AlterTable
ALTER TABLE "event" ALTER COLUMN "villageId" SET DEFAULT 'desa1';

View File

@@ -48,6 +48,7 @@ model Division {
description String? description String?
color String @default("#1E3A5F") color String @default("#1E3A5F")
isActive Boolean @default(true) isActive Boolean @default(true)
externalActivityCount Int @default(0) // Total kegiatan dari sistem NOC (misal: 47)
lastSyncedAt DateTime? // Terakhir kali sinkronisasi dilakukan lastSyncedAt DateTime? // Terakhir kali sinkronisasi dilakukan
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt

View File

@@ -0,0 +1,22 @@
import { prisma } from "../src/utils/db";
async function check() {
console.log("--- Checking Division Data in DB ---");
const divisions = await prisma.division.findMany({
select: {
name: true,
externalActivityCount: true,
}
});
console.table(divisions);
console.log("\n--- Checking API Response for /api/division/ ---");
// Mocking the mapping logic from src/api/division.ts
const formatted = divisions.map(d => ({
name: d.name,
activityCount: d.externalActivityCount
}));
console.table(formatted);
}
check().catch(console.error).finally(() => prisma.$disconnect());

View File

@@ -58,12 +58,14 @@ async function syncActiveDivisions() {
externalId: extId, externalId: extId,
color: div.color || "#1E3A5F", color: div.color || "#1E3A5F",
villageId: ID_DESA, villageId: ID_DESA,
externalActivityCount: div.totalKegiatan || 0,
}, },
create: { create: {
externalId: extId, externalId: extId,
name: name, name: name,
color: div.color || "#1E3A5F", color: div.color || "#1E3A5F",
villageId: ID_DESA, villageId: ID_DESA,
externalActivityCount: div.totalKegiatan || 0,
}, },
}); });
} }

View File

@@ -16,7 +16,12 @@ export const division = new Elysia({
}, },
}, },
}); });
return { data: divisions }; return {
data: divisions.map(d => ({
...d,
activityCount: d.externalActivityCount || d._count.activities
}))
};
} catch (error) { } catch (error) {
logger.error({ error }, "Failed to fetch divisions"); logger.error({ error }, "Failed to fetch divisions");
set.status = 500; set.status = 500;

View File

@@ -20,6 +20,7 @@ interface DivisionData {
interface DivisionApiResponse { interface DivisionApiResponse {
id: string; id: string;
name: string; name: string;
activityCount: number;
_count?: { _count?: {
activities: number; activities: number;
}; };
@@ -40,7 +41,7 @@ export function DivisionProgress() {
setData( setData(
(res.data.data as DivisionApiResponse[]).map((d) => ({ (res.data.data as DivisionApiResponse[]).map((d) => ({
name: d.name, name: d.name,
value: d._count?.activities || 0, value: d.activityCount || 0,
})), })),
); );
} }