upd: api noc #24
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