Files
dashboard-desaplus-noc/src/api/noc.ts

331 lines
6.5 KiB
TypeScript

import { Elysia, t } from "elysia";
import { prisma } from "../utils/db";
import { $ } from "bun";
export const noc = new Elysia({ prefix: "/noc" })
.post(
"/sync",
async ({ set, user }) => {
if (!user || user.role !== "admin") {
set.status = 401;
return { error: "Unauthorized" };
}
try {
// Jalankan script sinkronisasi
await $`bun run sync:noc`.quiet();
return {
success: true,
message: "Sinkronisasi berhasil diselesaikan",
lastSyncedAt: new Date().toISOString(),
};
} catch (error) {
return { success: false, error: "Sinkronisasi gagal dijalankan" };
}
},
{
response: {
200: t.Object({
success: t.Boolean(),
message: t.Optional(t.String()),
error: t.Optional(t.String()),
lastSyncedAt: t.Optional(t.String()),
}),
401: t.Object({ error: t.String() }),
},
},
)
.get(
"/last-sync",
async ({ query }) => {
const { idDesa } = query;
const latest = await prisma.division.findFirst({
where: { villageId: idDesa },
select: { lastSyncedAt: true },
orderBy: { lastSyncedAt: "desc" },
});
return { lastSyncedAt: latest?.lastSyncedAt?.toISOString() || null };
},
{
query: t.Object({ idDesa: t.String() }),
response: {
200: t.Object({
lastSyncedAt: t.Nullable(t.String()),
}),
},
},
)
.get(
"/active-divisions",
async ({ query }) => {
const { idDesa, limit } = query;
const data = await prisma.division.findMany({
where: { villageId: idDesa },
include: {
_count: {
select: { activities: true },
},
},
orderBy: {
activities: {
_count: "desc",
},
},
take: limit ? Number.parseInt(limit) : 5,
});
return {
data: data.map((d) => ({
id: d.id,
name: d.name,
activityCount: d._count.activities,
color: d.color,
})),
};
},
{
query: t.Object({
idDesa: t.String(),
limit: t.Optional(t.String()),
}),
response: {
200: t.Object({
data: t.Array(
t.Object({
id: t.String(),
name: t.String(),
activityCount: t.Number(),
color: t.String(),
}),
),
}),
},
},
)
.get(
"/latest-projects",
async ({ query }) => {
const { idDesa, limit } = query;
const data = await prisma.activity.findMany({
where: { villageId: idDesa },
orderBy: { createdAt: "desc" },
take: limit ? Number.parseInt(limit) : 5,
include: { division: true },
});
return {
data: data.map((a) => ({
id: a.id,
title: a.title,
status: a.status,
progress: a.progress,
divisionName: a.division.name,
createdAt: a.createdAt.toISOString(),
})),
};
},
{
query: t.Object({
idDesa: t.String(),
limit: t.Optional(t.String()),
}),
response: {
200: t.Object({
data: t.Array(
t.Object({
id: t.String(),
title: t.String(),
status: t.String(),
progress: t.Number(),
divisionName: t.String(),
createdAt: t.String(),
}),
),
}),
},
},
)
.get(
"/upcoming-events",
async ({ query }) => {
const { idDesa, limit, filter } = query;
const now = new Date();
const where: any = { villageId: idDesa };
if (filter === "today") {
const startOfDay = new Date(now.setHours(0, 0, 0, 0));
const endOfDay = new Date(now.setHours(23, 59, 59, 999));
where.startDate = {
gte: startOfDay,
lte: endOfDay,
};
} else {
where.startDate = {
gte: now,
};
}
const data = await prisma.event.findMany({
where,
orderBy: { startDate: "asc" },
take: limit ? Number.parseInt(limit) : 5,
});
return {
data: data.map((e) => ({
id: e.id,
title: e.title,
startDate: e.startDate.toISOString(),
location: e.location,
eventType: e.eventType,
})),
};
},
{
query: t.Object({
idDesa: t.String(),
limit: t.Optional(t.String()),
filter: t.Optional(t.String()), // today/upcoming
}),
response: {
200: t.Object({
data: t.Array(
t.Object({
id: t.String(),
title: t.String(),
startDate: t.String(),
location: t.Nullable(t.String()),
eventType: t.String(),
}),
),
}),
},
},
)
.get(
"/diagram-jumlah-document",
async ({ query }) => {
const { idDesa } = query;
const data = await prisma.document.groupBy({
where: { villageId: idDesa },
by: ["category"],
_count: {
_all: true,
},
});
return {
data: data.map((d) => ({
category: d.category,
count: d._count._all,
})),
};
},
{
query: t.Object({
idDesa: t.String(),
}),
response: {
200: t.Object({
data: t.Array(
t.Object({
category: t.String(),
count: t.Number(),
}),
),
}),
},
},
)
.get(
"/diagram-progres-kegiatan",
async ({ query }) => {
const { idDesa } = query;
const data = await prisma.activity.groupBy({
where: { villageId: idDesa },
by: ["status"],
_avg: {
progress: true,
},
_count: {
_all: true,
},
});
return {
data: data.map((d) => ({
status: d.status,
avgProgress: d._avg.progress || 0,
count: d._count._all,
})),
};
},
{
query: t.Object({
idDesa: t.String(),
}),
response: {
200: t.Object({
data: t.Array(
t.Object({
status: t.String(),
avgProgress: t.Number(),
count: t.Number(),
}),
),
}),
},
},
)
.get(
"/latest-discussion",
async ({ query }) => {
const { idDesa, limit } = query;
const data = await prisma.discussion.findMany({
where: { villageId: idDesa },
orderBy: { createdAt: "desc" },
take: limit ? Number.parseInt(limit) : 5,
include: {
sender: {
select: { name: true, image: true },
},
division: {
select: { name: true },
},
},
});
return {
data: data.map((d) => ({
id: d.id,
message: d.message,
senderName: d.sender.name || "Anonymous",
senderImage: d.sender.image,
divisionName: d.division?.name || "General",
createdAt: d.createdAt.toISOString(),
})),
};
},
{
query: t.Object({
idDesa: t.String(),
limit: t.Optional(t.String()),
}),
response: {
200: t.Object({
data: t.Array(
t.Object({
id: t.String(),
message: t.String(),
senderName: t.String(),
senderImage: t.Nullable(t.String()),
divisionName: t.String(),
createdAt: t.String(),
}),
),
}),
},
},
);