import { prisma } from "../src/utils/db"; import { nocExternalClient } from "../src/utils/noc-external-client"; import logger from "../src/utils/logger"; const ID_DESA = "desa1"; /** * Helper untuk mendapatkan system user ID untuk relasi */ async function getSystemUserId() { const user = await prisma.user.findFirst({ where: { role: "admin" }, }); if (!user) { // Buat system user jika tidak ada const newUser = await prisma.user.create({ data: { email: "system@desa1.id", name: "System Sync", role: "admin", }, }); return newUser.id; } return user.id; } /** * 1. Sync Divisions */ async function syncActiveDivisions() { logger.info("Syncing Divisions..."); const { data, error } = await nocExternalClient.GET("/api/noc/active-divisions", { params: { query: { idDesa: ID_DESA } }, }); if (error || !data) { logger.error({ error }, "Failed to fetch divisions from NOC"); return; } // biome-ignore lint/suspicious/noExplicitAny: External API response is untyped const resData = (data as any).data; const divisions = Array.isArray(resData) ? resData : (resData?.divisi || []); if (!Array.isArray(divisions)) { logger.warn({ data }, "Divisions data from NOC is not an array"); return; } for (const div of divisions) { const name = div.name || div.division; const extId = div.id || div.externalId || `div-${name.toLowerCase().replace(/\s+/g, "-")}`; await prisma.division.upsert({ where: { name: name }, update: { externalId: extId, color: div.color || "#1E3A5F", villageId: ID_DESA, externalActivityCount: div.totalKegiatan || 0, }, create: { externalId: extId, name: name, color: div.color || "#1E3A5F", villageId: ID_DESA, externalActivityCount: div.totalKegiatan || 0, }, }); } logger.info(`Synced ${divisions.length} divisions`); } /** * 2. Sync Activities */ async function syncLatestProjects() { logger.info("Syncing Activities..."); const { data, error } = await nocExternalClient.GET("/api/noc/latest-projects", { params: { query: { idDesa: ID_DESA, limit: "50" } }, }); if (error || !data) { logger.error({ error }, "Failed to fetch projects from NOC"); return; } // biome-ignore lint/suspicious/noExplicitAny: External API response const resData = (data as any).data; const projects = Array.isArray(resData) ? resData : (resData?.projects || []); if (!Array.isArray(projects)) { logger.warn({ data }, "Projects data from NOC is not an array"); return; } for (const proj of projects) { const extId = proj.id || proj.externalId || `proj-${proj.title.toLowerCase().replace(/\s+/g, "-")}`; // Temukan divisi lokal berdasarkan nama atau externalId const divisionName = proj.divisionName || proj.group; const division = await prisma.division.findFirst({ where: { name: divisionName }, }); if (!division) continue; await prisma.activity.upsert({ where: { externalId: extId }, update: { title: proj.title, status: (typeof proj.status === 'number' ? (proj.status === 2 ? 'Completed' : 'OnProgress') : proj.status) as any, progress: proj.progress || (proj.status === 2 ? 100 : 50), divisionId: division.id, villageId: ID_DESA, }, create: { externalId: extId, title: proj.title, status: (typeof proj.status === 'number' ? (proj.status === 2 ? 'Completed' : 'OnProgress') : proj.status) as any, progress: proj.progress || (proj.status === 2 ? 100 : 50), divisionId: division.id, villageId: ID_DESA, }, }); } logger.info(`Synced ${projects.length} activities`); } /** * 3. Sync Events */ async function syncUpcomingEvents() { logger.info("Syncing Events..."); const systemUserId = await getSystemUserId(); const { data, error } = await nocExternalClient.GET("/api/noc/upcoming-events", { params: { query: { idDesa: ID_DESA, limit: "50" } }, }); if (error || !data) { logger.error({ error }, "Failed to fetch events from NOC"); return; } // biome-ignore lint/suspicious/noExplicitAny: External API response const resData = (data as any).data; let events: any[] = []; if (Array.isArray(resData)) { events = resData; } else if (resData?.today || resData?.upcoming) { events = [...(resData.today || []), ...(resData.upcoming || [])]; } for (const event of events) { const extId = event.id || event.externalId || `event-${event.title.toLowerCase().replace(/\s+/g, "-")}`; await prisma.event.upsert({ where: { externalId: extId }, update: { title: event.title, startDate: new Date(event.startDate || event.date), location: event.location || "N/A", eventType: (event.eventType || "Meeting") as any, villageId: ID_DESA, }, create: { externalId: extId, title: event.title, startDate: new Date(event.startDate || event.date), location: event.location || "N/A", eventType: (event.eventType || "Meeting") as any, createdBy: systemUserId, villageId: ID_DESA, }, }); } logger.info(`Synced ${events.length} events`); } /** * 4. Sync Discussions */ async function syncLatestDiscussion() { logger.info("Syncing Discussions..."); const systemUserId = await getSystemUserId(); const { data, error } = await nocExternalClient.GET("/api/noc/latest-discussion", { params: { query: { idDesa: ID_DESA, limit: "50" } }, }); if (error || !data) { logger.error({ error }, "Failed to fetch discussions from NOC"); return; } // biome-ignore lint/suspicious/noExplicitAny: External API response const resData = (data as any).data; const discussions = Array.isArray(resData) ? resData : (resData?.discussions || resData?.data || []); if (!Array.isArray(discussions)) { logger.warn({ data }, "Discussions data from NOC is not an array"); return; } for (const disc of discussions) { const division = await prisma.division.findFirst({ where: { name: disc.divisionName || disc.group }, }); await prisma.discussion.upsert({ where: { externalId: disc.id }, update: { message: disc.message || disc.desc || disc.title, divisionId: division?.id, villageId: ID_DESA, }, create: { externalId: disc.id, message: disc.message || disc.desc || disc.title, senderId: systemUserId, divisionId: division?.id, villageId: ID_DESA, }, }); } logger.info(`Synced ${discussions.length} discussions`); } /** * 5. Sync Document Stats (New) */ async function syncDocumentStats() { logger.info("Syncing Document Stats..."); const { data, error } = await nocExternalClient.GET("/api/noc/diagram-jumlah-document", { params: { query: { idDesa: ID_DESA } }, }); if (error || !data) { logger.error({ error }, "Failed to fetch document stats from NOC"); return; } // biome-ignore lint/suspicious/noExplicitAny: External API response const resData = (data as any).data; if (!Array.isArray(resData)) { logger.warn({ data }, "Document stats data from NOC is not an array"); return; } for (const stat of resData) { await prisma.documentStat.upsert({ where: { villageId_label: { villageId: ID_DESA, label: stat.label, }, }, update: { value: stat.value, color: stat.color, }, create: { villageId: ID_DESA, label: stat.label, value: stat.value, color: stat.color, }, }); } logger.info(`Synced ${resData.length} document stats`); } /** * 6. Update lastSyncedAt timestamp */ async function syncLastTimestamp() { logger.info("Updating sync timestamp..."); await prisma.division.updateMany({ where: { villageId: ID_DESA }, data: { lastSyncedAt: new Date() }, }); } /** * Main Sync Function */ async function main() { try { logger.info("Starting NOC Data Synchronization..."); await syncActiveDivisions(); await syncLatestProjects(); await syncUpcomingEvents(); await syncLatestDiscussion(); await syncDocumentStats(); await syncLastTimestamp(); logger.info("NOC Data Synchronization Completed Successfully"); } catch (err) { logger.error({ err }, "Fatal error during NOC synchronization"); process.exit(1); } finally { await prisma.$disconnect(); } } main();