331 lines
6.5 KiB
TypeScript
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(),
|
|
}),
|
|
),
|
|
}),
|
|
},
|
|
},
|
|
);
|