diff --git a/__tests__/api/noc.test.ts b/__tests__/api/noc.test.ts
index 01a7cd4..283ab07 100644
--- a/__tests__/api/noc.test.ts
+++ b/__tests__/api/noc.test.ts
@@ -3,7 +3,7 @@ import api from "@/api";
import { prisma } from "@/utils/db";
describe("NOC API Module", () => {
- const idDesa = "darmasaba";
+ const idDesa = "desa1";
it("should return last sync timestamp", async () => {
const response = await api.handle(
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index e92ee40..e47612e 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -43,7 +43,7 @@ model User {
model Division {
id String @id @default(cuid())
externalId String? @unique // ID asli dari server NOC
- villageId String? @default("darmasaba") // ID Desa dari sistem NOC
+ villageId String? @default("desa1") // ID Desa dari sistem NOC
name String @unique
description String?
color String @default("#1E3A5F")
@@ -63,7 +63,7 @@ model Division {
model Activity {
id String @id @default(cuid())
externalId String? @unique // ID asli dari server NOC
- villageId String? @default("darmasaba")
+ villageId String? @default("desa1")
title String
description String?
divisionId String
@@ -88,7 +88,7 @@ model Activity {
model Document {
id String @id @default(cuid())
externalId String? @unique // ID asli dari server NOC
- villageId String? @default("darmasaba")
+ villageId String? @default("desa1")
title String
category DocumentCategory
type String // "Gambar", "Dokumen", "PDF", etc
@@ -109,7 +109,7 @@ model Document {
model Discussion {
id String @id @default(cuid())
externalId String? @unique // ID asli dari server NOC
- villageId String? @default("darmasaba")
+ villageId String? @default("desa1")
message String
senderId String
parentId String? // For threaded discussions
@@ -131,7 +131,7 @@ model Discussion {
model Event {
id String @id @default(cuid())
externalId String? @unique // ID asli dari server NOC
- villageId String? @default("darmasaba")
+ villageId String? @default("desa1")
title String
description String?
eventType EventType
diff --git a/scripts/inspect-noc-data.ts b/scripts/inspect-noc-data.ts
new file mode 100644
index 0000000..069e281
--- /dev/null
+++ b/scripts/inspect-noc-data.ts
@@ -0,0 +1,38 @@
+import { nocExternalClient } from "../src/utils/noc-external-client";
+
+async function inspect() {
+ const ID_DESA = "desa1";
+ console.log("Checking NOC API Data structure...");
+
+ const endpoints = [
+ "/api/noc/active-divisions",
+ "/api/noc/latest-projects",
+ "/api/noc/upcoming-events",
+ "/api/noc/latest-discussion"
+ ];
+
+ for (const endpoint of endpoints) {
+ console.log(`\n--- Endpoint: ${endpoint} ---`);
+ try {
+ const { data, error } = await (nocExternalClient as any).GET(endpoint, {
+ params: { query: { idDesa: ID_DESA, limit: "1" } }
+ });
+
+ if (error) {
+ console.error(`Error fetching ${endpoint}:`, error);
+ continue;
+ }
+
+ if (data && data.data && data.data.length > 0) {
+ console.log("Sample Data Object Keys:", Object.keys(data.data[0]));
+ console.log("Sample Data Object Values:", JSON.stringify(data.data[0], null, 2));
+ } else {
+ console.log("No data returned or data is empty.");
+ }
+ } catch (err) {
+ console.error(`Failed to fetch ${endpoint}:`, err);
+ }
+ }
+}
+
+inspect();
diff --git a/scripts/reset-noc-data.ts b/scripts/reset-noc-data.ts
new file mode 100644
index 0000000..77214b7
--- /dev/null
+++ b/scripts/reset-noc-data.ts
@@ -0,0 +1,38 @@
+import { prisma } from "../src/utils/db";
+import logger from "../src/utils/logger";
+
+async function resetNocData() {
+ try {
+ logger.info("Starting NOC Data Reset...");
+
+ // Delete in order to respect relations
+ // 1. Delete Activities (though Division cascade might handle it, let's be explicit)
+ const deletedActivities = await prisma.activity.deleteMany({});
+ logger.info(`Deleted ${deletedActivities.count} activities`);
+
+ // 2. Delete Documents
+ const deletedDocuments = await prisma.document.deleteMany({});
+ logger.info(`Deleted ${deletedDocuments.count} documents`);
+
+ // 3. Delete Discussions
+ const deletedDiscussions = await prisma.discussion.deleteMany({});
+ logger.info(`Deleted ${deletedDiscussions.count} discussions`);
+
+ // 4. Delete Events
+ const deletedEvents = await prisma.event.deleteMany({});
+ logger.info(`Deleted ${deletedEvents.count} events`);
+
+ // 5. Delete Divisions
+ const deletedDivisions = await prisma.division.deleteMany({});
+ logger.info(`Deleted ${deletedDivisions.count} divisions`);
+
+ logger.info("NOC Data Reset Completed Successfully");
+ } catch (err) {
+ logger.error({ err }, "Error during NOC data reset");
+ process.exit(1);
+ } finally {
+ await prisma.$disconnect();
+ }
+}
+
+resetNocData();
diff --git a/scripts/sync-noc.ts b/scripts/sync-noc.ts
index ff5552a..8653b74 100644
--- a/scripts/sync-noc.ts
+++ b/scripts/sync-noc.ts
@@ -2,7 +2,7 @@ import { prisma } from "../src/utils/db";
import { nocExternalClient } from "../src/utils/noc-external-client";
import logger from "../src/utils/logger";
-const ID_DESA = "darmasaba";
+const ID_DESA = "desa1";
/**
* Helper untuk mendapatkan system user ID untuk relasi
@@ -15,7 +15,7 @@ async function getSystemUserId() {
// Buat system user jika tidak ada
const newUser = await prisma.user.create({
data: {
- email: "system@darmasaba.id",
+ email: "system@desa1.id",
name: "System Sync",
role: "admin",
},
@@ -40,19 +40,29 @@ async function syncActiveDivisions() {
}
// biome-ignore lint/suspicious/noExplicitAny: External API response is untyped
- const divisions = (data as any).data;
+ 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: { externalId: div.id },
+ where: { name: name },
update: {
- name: div.name,
- color: div.color,
+ externalId: extId,
+ color: div.color || "#1E3A5F",
villageId: ID_DESA,
},
create: {
- externalId: div.id,
- name: div.name,
- color: div.color,
+ externalId: extId,
+ name: name,
+ color: div.color || "#1E3A5F",
villageId: ID_DESA,
},
});
@@ -75,30 +85,39 @@ async function syncLatestProjects() {
}
// biome-ignore lint/suspicious/noExplicitAny: External API response
- const projects = (data as any).data;
+ 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) {
- // Temukan divisi lokal berdasarkan nama atau externalId (asumsi externalId divisi sinkron)
- // Karena kita sinkron divisi dulu, kita cari berdasarkan nama jika externalId belum pasti
+ 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: proj.divisionName },
+ where: { name: divisionName },
});
if (!division) continue;
await prisma.activity.upsert({
- where: { externalId: proj.id },
+ where: { externalId: extId },
update: {
title: proj.title,
- status: proj.status as any,
- progress: proj.progress,
+ 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: proj.id,
+ externalId: extId,
title: proj.title,
- status: proj.status as any,
- progress: proj.progress,
+ 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,
},
@@ -123,23 +142,31 @@ async function syncUpcomingEvents() {
}
// biome-ignore lint/suspicious/noExplicitAny: External API response
- const events = (data as any).data;
+ 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: event.id },
+ where: { externalId: extId },
update: {
title: event.title,
- startDate: new Date(event.startDate),
- location: event.location,
- eventType: event.eventType as any,
+ startDate: new Date(event.startDate || event.date),
+ location: event.location || "N/A",
+ eventType: (event.eventType || "Meeting") as any,
villageId: ID_DESA,
},
create: {
- externalId: event.id,
+ externalId: extId,
title: event.title,
- startDate: new Date(event.startDate),
- location: event.location,
- eventType: event.eventType as any,
+ startDate: new Date(event.startDate || event.date),
+ location: event.location || "N/A",
+ eventType: (event.eventType || "Meeting") as any,
createdBy: systemUserId,
villageId: ID_DESA,
},
@@ -164,22 +191,29 @@ async function syncLatestDiscussion() {
}
// biome-ignore lint/suspicious/noExplicitAny: External API response
- const discussions = (data as any).data;
+ 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 },
+ where: { name: disc.divisionName || disc.group },
});
await prisma.discussion.upsert({
where: { externalId: disc.id },
update: {
- message: disc.message,
+ message: disc.message || disc.desc || disc.title,
divisionId: division?.id,
villageId: ID_DESA,
},
create: {
externalId: disc.id,
- message: disc.message,
+ message: disc.message || disc.desc || disc.title,
senderId: systemUserId,
divisionId: division?.id,
villageId: ID_DESA,
diff --git a/src/components/pengaturan/sinkronisasi.tsx b/src/components/pengaturan/sinkronisasi.tsx
index 1fa3939..0d8064b 100644
--- a/src/components/pengaturan/sinkronisasi.tsx
+++ b/src/components/pengaturan/sinkronisasi.tsx
@@ -31,7 +31,7 @@ const SinkronisasiSettings = () => {
const fetchLastSync = async () => {
const { data } = await apiClient.GET("/api/noc/last-sync", {
- params: { query: { idDesa: "darmasaba" } },
+ params: { query: { idDesa: "desa1" } },
});
if (data?.lastSyncedAt) {
setLastSync(data.lastSyncedAt);
@@ -158,7 +158,7 @@ const SinkronisasiSettings = () => {
ID Desa:
- darmasaba
+ desa1
Model Data:
diff --git a/src/utils/noc-external-client.ts b/src/utils/noc-external-client.ts
index d892ec2..d5e5d9c 100644
--- a/src/utils/noc-external-client.ts
+++ b/src/utils/noc-external-client.ts
@@ -6,13 +6,14 @@ import { getEnv } from "./env";
* NOC External Client
* Digunakan khusus untuk menarik data dari server NOC darmasaba.muku.id
*/
-const externalBaseUrl = getEnv(
- "NOC_API_URL",
- "https://darmasaba.muku.id/api/noc",
-);
+const externalBaseUrl = getEnv("NOC_API_URL", "https://darmasaba.muku.id/api/noc");
-// Hilangkan suffix /docs/json jika ada di URL
-const cleanBaseUrl = externalBaseUrl.replace("/docs/json", "");
+// Hilangkan path dokumentasi dan prefix /api/noc jika ada di URL base,
+// karena 'paths' di generated/noc-external.ts sudah menyertakan prefix /api/noc
+const cleanBaseUrl = externalBaseUrl
+ .replace("/api/noc/docs/json", "")
+ .replace("/docs/json", "")
+ .replace(/\/api\/noc\/?$/, "");
export const nocExternalClient = createClient({
baseUrl: cleanBaseUrl,