upd: api noc
This commit is contained in:
503
src/app/api/noc/[[...slug]]/route.ts
Normal file
503
src/app/api/noc/[[...slug]]/route.ts
Normal file
@@ -0,0 +1,503 @@
|
|||||||
|
import cors from "@elysiajs/cors";
|
||||||
|
import { swagger } from "@elysiajs/swagger";
|
||||||
|
import Elysia, { t } from "elysia";
|
||||||
|
import { prisma } from "@/module/_global";
|
||||||
|
import moment from "moment";
|
||||||
|
import "moment/locale/id";
|
||||||
|
|
||||||
|
// Gabungkan semua ke dalam satu instance server yang dipasang di /api/noc
|
||||||
|
const NocServer = new Elysia({ prefix: "/api/noc" })
|
||||||
|
.use(cors({
|
||||||
|
origin: "*",
|
||||||
|
methods: ["GET", "POST", "OPTIONS"],
|
||||||
|
}))
|
||||||
|
.use(swagger({
|
||||||
|
path: "/docs", // Karena prefix instance adalah /api/noc, maka ini akan diakses di /api/noc/docs
|
||||||
|
documentation: {
|
||||||
|
info: {
|
||||||
|
title: "Sistem Desa Mandiri - NOC API",
|
||||||
|
version: "1.0.0",
|
||||||
|
description: "API Khusus untuk kebutuhan NOC (Network Operation Center) dan Monitoring Desa",
|
||||||
|
},
|
||||||
|
tags: [
|
||||||
|
{ name: "NOC", description: "Endpoint khusus monitoring" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// ── GET /api/noc/active-divisions ──────────────────────────────────────────
|
||||||
|
.get(
|
||||||
|
"/active-divisions",
|
||||||
|
async ({ query, set }) => {
|
||||||
|
const { idDesa, limit } = query;
|
||||||
|
|
||||||
|
if (!idDesa) {
|
||||||
|
set.status = 400;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Parameter idDesa wajib diisi",
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxResults = Number(limit ?? 5);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Cek apakah desa ada
|
||||||
|
const village = await prisma.village.findUnique({
|
||||||
|
where: { id: idDesa },
|
||||||
|
select: { id: true, name: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!village) {
|
||||||
|
set.status = 404;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Desa tidak ditemukan",
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambil semua divisi milik desa ini
|
||||||
|
const divisions = await prisma.division.findMany({
|
||||||
|
where: {
|
||||||
|
idVillage: idDesa,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
idGroup: true,
|
||||||
|
Group: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
DivisionProject: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hitung total kegiatan per divisi & urutkan descending, ambil top sesuai limit
|
||||||
|
const ranked = divisions
|
||||||
|
.map((d) => ({
|
||||||
|
id: d.id,
|
||||||
|
division: d.name,
|
||||||
|
group: d.Group.name,
|
||||||
|
totalKegiatan: d._count.DivisionProject
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.totalKegiatan - a.totalKegiatan)
|
||||||
|
.slice(0, maxResults);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil mendapatkan divisi teraktif",
|
||||||
|
data: {
|
||||||
|
idDesa: village.id,
|
||||||
|
namaDesa: village.name,
|
||||||
|
divisi: ranked,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[NOC] active-divisions error:", error);
|
||||||
|
set.status = 500;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Terjadi kesalahan pada server",
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: t.Object({
|
||||||
|
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
|
||||||
|
limit: t.Optional(t.String({ description: "Jumlah maksimal data (default: 5)" })),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Divisi Teraktif",
|
||||||
|
description: "Mendapatkan daftar divisi teraktif berdasarkan jumlah proyek pada desa tertentu.",
|
||||||
|
tags: ["NOC"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ── GET /api/noc/latest-projects ──────────────────────────────────────────
|
||||||
|
.get(
|
||||||
|
"/latest-projects",
|
||||||
|
async ({ query, set }) => {
|
||||||
|
const { idDesa, limit } = query;
|
||||||
|
|
||||||
|
if (!idDesa) {
|
||||||
|
set.status = 400;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Parameter idDesa wajib diisi",
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxResults = Math.min(Number(limit ?? 5), 50);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Cek apakah desa ada
|
||||||
|
const village = await prisma.village.findUnique({
|
||||||
|
where: { id: idDesa },
|
||||||
|
select: { id: true, name: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!village) {
|
||||||
|
set.status = 404;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Desa tidak ditemukan",
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ambil proyek umum terbaru dari desa ini
|
||||||
|
const projects = await prisma.project.findMany({
|
||||||
|
where: {
|
||||||
|
idVillage: idDesa,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
title: true,
|
||||||
|
status: true,
|
||||||
|
desc: true,
|
||||||
|
updatedAt: true,
|
||||||
|
Group: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
User: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
updatedAt: "desc",
|
||||||
|
},
|
||||||
|
take: maxResults,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapped = projects.map((p) => ({
|
||||||
|
id: p.id,
|
||||||
|
title: p.title,
|
||||||
|
status: p.status,
|
||||||
|
desc: p.desc,
|
||||||
|
group: p.Group.name,
|
||||||
|
createdBy: p.User.name,
|
||||||
|
updatedAt: p.updatedAt,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil mendapatkan proyek terbaru",
|
||||||
|
data: {
|
||||||
|
idDesa: village.id,
|
||||||
|
namaDesa: village.name,
|
||||||
|
total: mapped.length,
|
||||||
|
projects: mapped,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[NOC] latest-projects error:", error);
|
||||||
|
set.status = 500;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Terjadi kesalahan pada server",
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: t.Object({
|
||||||
|
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
|
||||||
|
limit: t.Optional(
|
||||||
|
t.String({ description: "Jumlah maksimal proyek (default: 5, maks: 50)" })
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Latest Projects General",
|
||||||
|
description: "Mendapatkan daftar proyek umum terbaru dari berbagai grup pada desa tertentu.",
|
||||||
|
tags: ["NOC"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ── GET /api/noc/village-summary ───────────────────────────────────────────
|
||||||
|
.get(
|
||||||
|
"/village-summary",
|
||||||
|
async ({ query, set }) => {
|
||||||
|
const { idDesa } = query;
|
||||||
|
|
||||||
|
if (!idDesa) {
|
||||||
|
set.status = 400;
|
||||||
|
return { success: false, message: "Parameter idDesa wajib diisi", data: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const counts = await prisma.village.findUnique({
|
||||||
|
where: { id: idDesa },
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
_count: {
|
||||||
|
select: {
|
||||||
|
User: true,
|
||||||
|
Group: true,
|
||||||
|
Division: true,
|
||||||
|
Project: true,
|
||||||
|
Announcement: true,
|
||||||
|
Discussion: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!counts) {
|
||||||
|
set.status = 404;
|
||||||
|
return { success: false, message: "Desa tidak ditemukan", data: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil mendapatkan ringkasan desa",
|
||||||
|
data: {
|
||||||
|
idDesa,
|
||||||
|
namaDesa: counts.name,
|
||||||
|
summary: {
|
||||||
|
totalWarga: counts._count.User,
|
||||||
|
totalGrup: counts._count.Group,
|
||||||
|
totalDivisi: counts._count.Division,
|
||||||
|
totalProyek: counts._count.Project,
|
||||||
|
totalPengumuman: counts._count.Announcement,
|
||||||
|
totalDiskusi: counts._count.Discussion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[NOC] village-summary error:", error);
|
||||||
|
set.status = 500;
|
||||||
|
return { success: false, message: "Terjadi kesalahan pada server", data: null };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: t.Object({ idDesa: t.String() }),
|
||||||
|
detail: { summary: "Village Summary Statistics", tags: ["NOC"] }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ── GET /api/noc/recent-activity ───────────────────────────────────────────
|
||||||
|
.get(
|
||||||
|
"/recent-activity",
|
||||||
|
async ({ query, set }) => {
|
||||||
|
const { idDesa, limit } = query;
|
||||||
|
|
||||||
|
if (!idDesa) {
|
||||||
|
set.status = 400;
|
||||||
|
return { success: false, message: "Parameter idDesa wajib diisi", data: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxResults = Math.min(Number(limit ?? 10), 50);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const logs = await prisma.userLog.findMany({
|
||||||
|
where: {
|
||||||
|
User: {
|
||||||
|
idVillage: idDesa
|
||||||
|
}
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
action: true,
|
||||||
|
desc: true,
|
||||||
|
createdAt: true,
|
||||||
|
User: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
img: true,
|
||||||
|
Group: { select: { name: true } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
orderBy: { createdAt: "desc" },
|
||||||
|
take: maxResults
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapped = logs.map(l => ({
|
||||||
|
id: l.id,
|
||||||
|
userName: l.User.name,
|
||||||
|
userGroup: l.User.Group.name,
|
||||||
|
userImg: l.User.img,
|
||||||
|
action: l.action,
|
||||||
|
description: l.desc,
|
||||||
|
time: moment(l.createdAt).fromNow(),
|
||||||
|
date: moment(l.createdAt).format("YYYY-MM-DD HH:mm:ss")
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil mendapatkan aktivitas terbaru",
|
||||||
|
data: { idDesa, total: mapped.length, activities: mapped }
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[NOC] recent-activity error:", error);
|
||||||
|
set.status = 500;
|
||||||
|
return { success: false, message: "Terjadi kesalahan pada server", data: null };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: t.Object({
|
||||||
|
idDesa: t.String(),
|
||||||
|
limit: t.Optional(t.String())
|
||||||
|
}),
|
||||||
|
detail: { summary: "Recent User Activity Logs", tags: ["NOC"] }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ── GET /api/noc/upcoming-events ───────────────────────────────────────────
|
||||||
|
.get(
|
||||||
|
"/upcoming-events",
|
||||||
|
async ({ query, set }) => {
|
||||||
|
const { idDesa, limit } = query;
|
||||||
|
|
||||||
|
if (!idDesa) {
|
||||||
|
set.status = 400;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Parameter idDesa wajib diisi",
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const maxResults = Math.min(Number(limit ?? 10), 50);
|
||||||
|
const today = moment().startOf("day").toDate();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const village = await prisma.village.findUnique({
|
||||||
|
where: { id: idDesa },
|
||||||
|
select: { id: true, name: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!village) {
|
||||||
|
set.status = 404;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Desa tidak ditemukan",
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const events = await prisma.divisionCalendarReminder.findMany({
|
||||||
|
where: {
|
||||||
|
isActive: true,
|
||||||
|
dateStart: {
|
||||||
|
gte: today,
|
||||||
|
},
|
||||||
|
Division: {
|
||||||
|
idVillage: idDesa,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
DivisionCalendar: {
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
idCalendar: true,
|
||||||
|
dateStart: true,
|
||||||
|
dateEnd: true,
|
||||||
|
timeStart: true,
|
||||||
|
timeEnd: true,
|
||||||
|
status: true,
|
||||||
|
Division: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
DivisionCalendar: {
|
||||||
|
select: {
|
||||||
|
title: true,
|
||||||
|
desc: true,
|
||||||
|
linkMeet: true,
|
||||||
|
repeatEventTyper: true,
|
||||||
|
User: {
|
||||||
|
select: {
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
orderBy: [
|
||||||
|
{ dateStart: "asc" },
|
||||||
|
{ timeStart: "asc" },
|
||||||
|
],
|
||||||
|
take: maxResults,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapped = events.map((e) => ({
|
||||||
|
id: e.id,
|
||||||
|
idCalendar: e.idCalendar,
|
||||||
|
title: e.DivisionCalendar.title,
|
||||||
|
desc: e.DivisionCalendar.desc,
|
||||||
|
linkMeet: e.DivisionCalendar.linkMeet ?? null,
|
||||||
|
repeatEventTyper: e.DivisionCalendar.repeatEventTyper,
|
||||||
|
dateStart: moment(e.dateStart).format("YYYY-MM-DD"),
|
||||||
|
dateEnd: e.dateEnd
|
||||||
|
? moment(e.dateEnd).format("YYYY-MM-DD")
|
||||||
|
: null,
|
||||||
|
timeStart: moment.utc(e.timeStart).format("HH:mm"),
|
||||||
|
timeEnd: moment.utc(e.timeEnd).format("HH:mm"),
|
||||||
|
status: e.status,
|
||||||
|
createdBy: e.DivisionCalendar.User.name,
|
||||||
|
divisi: {
|
||||||
|
id: e.Division.id,
|
||||||
|
name: e.Division.name,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: "Berhasil mendapatkan upcoming events",
|
||||||
|
data: {
|
||||||
|
idDesa: village.id,
|
||||||
|
namaDesa: village.name,
|
||||||
|
total: mapped.length,
|
||||||
|
events: mapped,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[NOC] upcoming-events error:", error);
|
||||||
|
set.status = 500;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "Terjadi kesalahan pada server",
|
||||||
|
data: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
query: t.Object({
|
||||||
|
idDesa: t.String({ description: "ID Desa yang ingin dicari" }),
|
||||||
|
limit: t.Optional(
|
||||||
|
t.String({ description: "Jumlah maksimal event (default: 10, maks: 50)" })
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
summary: "Upcoming Events",
|
||||||
|
description: "Mendapatkan daftar event yang akan datang untuk semua divisi pada desa tertentu.",
|
||||||
|
tags: ["NOC"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const GET = NocServer.handle;
|
||||||
|
export const POST = NocServer.handle;
|
||||||
Reference in New Issue
Block a user