1440 lines
58 KiB
TypeScript
1440 lines
58 KiB
TypeScript
import { isValidApiKey } from "@/lib/apiKey";
|
|
import { prisma } from "@/module/_global";
|
|
import cors from "@elysiajs/cors";
|
|
import { swagger } from "@elysiajs/swagger";
|
|
import Elysia, { t } from "elysia";
|
|
import _ from "lodash";
|
|
import moment from "moment";
|
|
import "moment/locale/id";
|
|
|
|
const AiServer = new Elysia({ prefix: "/api/ai" })
|
|
.use(cors({
|
|
origin: "*",
|
|
methods: ["GET", "OPTIONS"],
|
|
allowedHeaders: ["Content-Type", "x-api-key"],
|
|
}))
|
|
.use(swagger({
|
|
path: "/docs",
|
|
documentation: {
|
|
info: {
|
|
title: "Desa Plus - Jenna Perangkat Desa API",
|
|
version: "1.0.0",
|
|
description: "API untuk kebutuhan integrasi Jenna Perangkat Desa — data desa, divisi, proyek, dll.",
|
|
},
|
|
components: {
|
|
securitySchemes: {
|
|
ApiKeyAuth: {
|
|
type: "apiKey",
|
|
in: "header",
|
|
name: "x-api-key",
|
|
},
|
|
},
|
|
},
|
|
security: [{ ApiKeyAuth: [] }],
|
|
},
|
|
}))
|
|
.onBeforeHandle(async ({ request, set, path }) => {
|
|
if (path.startsWith("/api/ai/docs")) return;
|
|
|
|
const incoming = request.headers.get("x-api-key");
|
|
if (!incoming || !(await isValidApiKey(incoming))) {
|
|
set.status = 401;
|
|
return { success: false, message: "Unauthorized" };
|
|
}
|
|
})
|
|
|
|
// ─── ANNOUNCEMENT ────────────────────────────────────────────────────────
|
|
|
|
.get("/announcement", async ({ query, set }) => {
|
|
try {
|
|
const { search, page, get, desa, active } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
const data = await prisma.announcement.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: {
|
|
idVillage: String(desa),
|
|
isActive: active === "false" ? false : true,
|
|
title: { contains: search ?? "", mode: "insensitive" },
|
|
},
|
|
orderBy: { createdAt: "desc" },
|
|
});
|
|
|
|
return { success: true, message: "Berhasil mendapatkan pengumuman", data };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan pengumuman", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci judul" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
active: t.Optional(t.String({ description: "Filter aktif (true/false)" })),
|
|
}),
|
|
detail: { summary: "List Pengumuman", tags: ["announcement"] },
|
|
})
|
|
|
|
.get("/announcement/:id", async ({ params, set }) => {
|
|
try {
|
|
const { id } = params;
|
|
|
|
const count = await prisma.announcement.count({ where: { id } });
|
|
if (count === 0) {
|
|
set.status = 404;
|
|
return { success: false, message: "Pengumuman tidak ditemukan" };
|
|
}
|
|
|
|
const announcement = await prisma.announcement.findUnique({
|
|
where: { id },
|
|
select: { id: true, title: true, desc: true },
|
|
});
|
|
|
|
const announcementMember = await prisma.announcementMember.findMany({
|
|
where: { idAnnouncement: id },
|
|
select: {
|
|
idGroup: true,
|
|
idDivision: true,
|
|
Group: { select: { name: true } },
|
|
Division: { select: { name: true } },
|
|
},
|
|
});
|
|
|
|
const member = announcementMember.map((v: any) => ({
|
|
..._.omit(v, ["Group", "Division"]),
|
|
group: v.Group.name,
|
|
division: v.Division.name,
|
|
}));
|
|
|
|
return { success: true, message: "Berhasil mendapatkan pengumuman", data: { ...announcement, member } };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan pengumuman", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
params: t.Object({ id: t.String({ description: "ID pengumuman" }) }),
|
|
detail: { summary: "Detail Pengumuman", tags: ["announcement"] },
|
|
})
|
|
|
|
// ─── BANNER ──────────────────────────────────────────────────────────────
|
|
|
|
.get("/banner", async ({ query, set }) => {
|
|
try {
|
|
const { search, page, get, desa, active } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
const data = await prisma.bannerImage.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: {
|
|
idVillage: String(desa),
|
|
isActive: active === "false" ? false : true,
|
|
title: { contains: search ?? "", mode: "insensitive" },
|
|
},
|
|
orderBy: { createdAt: "desc" },
|
|
});
|
|
|
|
return { success: true, message: "Berhasil mendapatkan banner", data };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan banner", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci judul" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
active: t.Optional(t.String({ description: "Filter aktif (true/false)" })),
|
|
}),
|
|
detail: { summary: "List Banner", tags: ["banner"] },
|
|
})
|
|
|
|
.get("/banner/:id", async ({ params, set }) => {
|
|
try {
|
|
const { id } = params;
|
|
const data = await prisma.bannerImage.findUnique({ where: { id: String(id) } });
|
|
return { success: true, message: "Berhasil mendapatkan banner", data };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan banner", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
params: t.Object({ id: t.String({ description: "ID banner" }) }),
|
|
detail: { summary: "Detail Banner", tags: ["banner"] },
|
|
})
|
|
|
|
// ─── CALENDAR ────────────────────────────────────────────────────────────
|
|
|
|
.get("/calendar", async ({ query, set }) => {
|
|
try {
|
|
const { division, date, desa, active, search, page, get } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
let kondisi: any = {};
|
|
|
|
const baseCalendar = {
|
|
title: { contains: search ?? "", mode: "insensitive" },
|
|
isActive: active === "false" ? false : true,
|
|
Division: { idVillage: String(desa) },
|
|
};
|
|
|
|
if (division) {
|
|
kondisi = { idDivision: division, ...(date && { dateStart: new Date(date) }), DivisionCalendar: baseCalendar };
|
|
} else {
|
|
kondisi = { ...(date && { dateStart: new Date(date) }), DivisionCalendar: baseCalendar };
|
|
}
|
|
|
|
const data = await prisma.divisionCalendarReminder.findMany({
|
|
where: kondisi,
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
select: {
|
|
id: true,
|
|
dateStart: true,
|
|
timeStart: true,
|
|
timeEnd: true,
|
|
createdAt: true,
|
|
DivisionCalendar: {
|
|
select: {
|
|
isActive: true,
|
|
title: true,
|
|
desc: true,
|
|
User: { select: { name: true } },
|
|
},
|
|
},
|
|
},
|
|
orderBy: [{ dateStart: "asc" }, { timeStart: "asc" }, { timeEnd: "asc" }],
|
|
});
|
|
|
|
const result = data.map((v: any) => ({
|
|
..._.omit(v, ["DivisionCalendar"]),
|
|
title: v.DivisionCalendar.title,
|
|
desc: v.DivisionCalendar.desc,
|
|
createdBy: v.DivisionCalendar.User.name,
|
|
isActive: v.DivisionCalendar.isActive,
|
|
timeStart: moment.utc(v.timeStart).format("HH:mm"),
|
|
timeEnd: moment.utc(v.timeEnd).format("HH:mm"),
|
|
}));
|
|
|
|
return { success: true, message: "Berhasil mendapatkan kalender", data: result };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan kalender", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
division: t.Optional(t.String({ description: "ID divisi" })),
|
|
date: t.Optional(t.String({ description: "Tanggal filter (ISO)" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci judul" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
active: t.Optional(t.String({ description: "Filter aktif (true/false)" })),
|
|
}),
|
|
detail: { summary: "List Kalender", tags: ["calendar"] },
|
|
})
|
|
|
|
.get("/calendar/:id", async ({ params, set }) => {
|
|
try {
|
|
const { id } = params;
|
|
|
|
const count = await prisma.divisionCalendarReminder.count({ where: { id } });
|
|
if (count === 0) {
|
|
set.status = 404;
|
|
return { success: false, message: "Acara tidak ditemukan" };
|
|
}
|
|
|
|
const data: any = await prisma.divisionCalendarReminder.findUnique({
|
|
where: { id },
|
|
select: {
|
|
id: true,
|
|
timeStart: true,
|
|
dateStart: true,
|
|
timeEnd: true,
|
|
createdAt: true,
|
|
DivisionCalendar: {
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
desc: true,
|
|
linkMeet: true,
|
|
repeatEventTyper: true,
|
|
repeatValue: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const { DivisionCalendar, ...dataCalendar } = data;
|
|
const result = {
|
|
...dataCalendar,
|
|
timeStart: moment.utc(dataCalendar.timeStart).format("HH:mm"),
|
|
timeEnd: moment.utc(dataCalendar.timeEnd).format("HH:mm"),
|
|
title: DivisionCalendar.title,
|
|
desc: DivisionCalendar.desc,
|
|
linkMeet: DivisionCalendar.linkMeet,
|
|
repeatEventTyper: DivisionCalendar.repeatEventTyper,
|
|
repeatValue: DivisionCalendar.repeatValue,
|
|
};
|
|
|
|
const memberRaw = await prisma.divisionCalendarMember.findMany({
|
|
where: { idCalendar: DivisionCalendar.id },
|
|
select: {
|
|
id: true,
|
|
idUser: true,
|
|
User: { select: { id: true, name: true, email: true, img: true } },
|
|
},
|
|
});
|
|
|
|
const member = memberRaw.map((v: any) => ({
|
|
..._.omit(v, ["User"]),
|
|
name: v.User.name,
|
|
email: v.User.email,
|
|
img: v.User.img,
|
|
}));
|
|
|
|
return { success: true, message: "Berhasil mendapatkan kalender", data: { ...result, member } };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan kalender", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
params: t.Object({ id: t.String({ description: "ID kalender reminder" }) }),
|
|
detail: { summary: "Detail Kalender", tags: ["calendar"] },
|
|
})
|
|
|
|
// ─── DISCUSSION GENERAL ──────────────────────────────────────────────────
|
|
|
|
.get("/discussion-general", async ({ query, set }) => {
|
|
try {
|
|
const { group, desa, search, page, status, active, get } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
let kondisi: any = {
|
|
isActive: active === "false" ? false : true,
|
|
status: status === "close" ? 2 : 1,
|
|
idVillage: String(desa),
|
|
title: { contains: !search || search === "null" ? "" : search, mode: "insensitive" },
|
|
};
|
|
|
|
if (group && group !== "null") {
|
|
kondisi.idGroup = String(group);
|
|
}
|
|
|
|
const data = await prisma.discussion.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: kondisi,
|
|
orderBy: [{ status: "desc" }, { createdAt: "desc" }],
|
|
select: {
|
|
id: true,
|
|
title: true,
|
|
desc: true,
|
|
status: true,
|
|
createdAt: true,
|
|
DiscussionComment: { select: { id: true } },
|
|
Group: { select: { name: true } },
|
|
},
|
|
});
|
|
|
|
const result = data.map((v: any) => ({
|
|
..._.omit(v, ["DiscussionComment", "status", "Group"]),
|
|
totalKomentar: v.DiscussionComment.length,
|
|
status: v.status === 1 ? "Open" : "Close",
|
|
group: v.Group.name,
|
|
}));
|
|
|
|
return { success: true, message: "Berhasil mendapatkan diskusi", data: result };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan diskusi", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
group: t.Optional(t.String({ description: "ID group" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
status: t.Optional(t.String({ description: "Status: open/close" })),
|
|
active: t.Optional(t.String({ description: "Filter aktif (true/false)" })),
|
|
}),
|
|
detail: { summary: "List Diskusi Umum", tags: ["discussion-general"] },
|
|
})
|
|
|
|
.get("/discussion-general/:id", async ({ params, query, set }) => {
|
|
try {
|
|
const { id } = params;
|
|
const { cat, desa } = query;
|
|
|
|
const count = await prisma.discussion.count({ where: { id, idVillage: String(desa) } });
|
|
if (count === 0) {
|
|
set.status = 404;
|
|
return { success: false, message: "Diskusi tidak ditemukan" };
|
|
}
|
|
|
|
if (cat === "comment") {
|
|
const data = await prisma.discussionComment.findMany({
|
|
where: { idDiscussion: id, isActive: true },
|
|
select: {
|
|
id: true,
|
|
comment: true,
|
|
createdAt: true,
|
|
idUser: true,
|
|
User: { select: { name: true, img: true } },
|
|
},
|
|
});
|
|
const result = data.map((v: any) => ({
|
|
..._.omit(v, ["User"]),
|
|
username: v.User.name,
|
|
img: v.User.img,
|
|
}));
|
|
return { success: true, message: "Berhasil mendapatkan diskusi", data: result };
|
|
}
|
|
|
|
if (cat === "member") {
|
|
const data = await prisma.discussionMember.findMany({
|
|
where: { idDiscussion: id, isActive: true },
|
|
select: { idUser: true, User: { select: { name: true, img: true } } },
|
|
});
|
|
const result = data.map((v: any) => ({
|
|
..._.omit(v, ["User"]),
|
|
name: v.User.name,
|
|
img: v.User.img,
|
|
}));
|
|
return { success: true, message: "Berhasil mendapatkan diskusi", data: result };
|
|
}
|
|
|
|
const data = await prisma.discussion.findUnique({
|
|
where: { id, idVillage: String(desa) },
|
|
select: {
|
|
isActive: true,
|
|
id: true,
|
|
title: true,
|
|
idGroup: true,
|
|
desc: true,
|
|
status: true,
|
|
createdAt: true,
|
|
Group: { select: { name: true } },
|
|
},
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
message: "Berhasil mendapatkan diskusi",
|
|
data: {
|
|
id: data?.id,
|
|
isActive: data?.isActive,
|
|
idGroup: data?.idGroup,
|
|
group: data?.Group.name,
|
|
title: data?.title,
|
|
desc: data?.desc,
|
|
status: data?.status === 1 ? "Open" : "Close",
|
|
createdAt: data?.createdAt,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan diskusi", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
params: t.Object({ id: t.String({ description: "ID diskusi umum" }) }),
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
cat: t.Optional(t.String({ description: "Kategori: comment / member" })),
|
|
}),
|
|
detail: { summary: "Detail Diskusi Umum", tags: ["discussion-general"] },
|
|
})
|
|
|
|
// ─── DISCUSSION (DIVISI) ─────────────────────────────────────────────────
|
|
|
|
.get("/discussion", async ({ query, set }) => {
|
|
try {
|
|
const { division, search, page, status, active, desa, get } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
let kondisi: any = {
|
|
isActive: active === "false" ? false : true,
|
|
status: status === "close" ? 2 : 1,
|
|
Division: { idVillage: String(desa) },
|
|
desc: { contains: !search || search === "null" ? "" : search, mode: "insensitive" },
|
|
};
|
|
|
|
if (division && division !== "null") {
|
|
kondisi.idDivision = division;
|
|
}
|
|
|
|
const data = await prisma.divisionDisscussion.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: kondisi,
|
|
orderBy: { createdAt: "desc" },
|
|
select: {
|
|
id: true,
|
|
desc: true,
|
|
status: true,
|
|
createdAt: true,
|
|
idDivision: true,
|
|
Division: { select: { name: true } },
|
|
DivisionDisscussionComment: { select: { id: true } },
|
|
},
|
|
});
|
|
|
|
const result = data.map((v: any) => ({
|
|
..._.omit(v, ["DivisionDisscussionComment", "status", "Division"]),
|
|
totalKomentar: v.DivisionDisscussionComment.length,
|
|
status: v.status === 1 ? "Open" : "Close",
|
|
division: v.Division.name,
|
|
}));
|
|
|
|
return { success: true, message: "Berhasil mendapatkan diskusi", data: result };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan diskusi", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
division: t.Optional(t.String({ description: "ID divisi" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
status: t.Optional(t.String({ description: "Status: open/close" })),
|
|
active: t.Optional(t.String({ description: "Filter aktif (true/false)" })),
|
|
}),
|
|
detail: { summary: "List Diskusi Divisi", tags: ["discussion"] },
|
|
})
|
|
|
|
.get("/discussion/:id", async ({ params, set }) => {
|
|
try {
|
|
const { id } = params;
|
|
|
|
const count = await prisma.divisionDisscussion.count({ where: { id } });
|
|
if (count === 0) {
|
|
set.status = 404;
|
|
return { success: false, message: "Diskusi tidak ditemukan" };
|
|
}
|
|
|
|
const data = await prisma.divisionDisscussion.findUnique({
|
|
where: { id },
|
|
select: {
|
|
isActive: true,
|
|
id: true,
|
|
desc: true,
|
|
status: true,
|
|
createdAt: true,
|
|
idDivision: true,
|
|
Division: { select: { name: true } },
|
|
User: { select: { name: true } },
|
|
DivisionDisscussionComment: {
|
|
select: {
|
|
id: true,
|
|
comment: true,
|
|
createdAt: true,
|
|
User: { select: { name: true, img: true } },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!data) {
|
|
set.status = 404;
|
|
return { success: false, message: "Diskusi tidak ditemukan" };
|
|
}
|
|
|
|
const komentar = data.DivisionDisscussionComment.map((c: any) => ({
|
|
id: c.id,
|
|
comment: c.comment,
|
|
createdAt: c.createdAt,
|
|
username: c.User.name,
|
|
userimg: c.User.img,
|
|
}));
|
|
|
|
return {
|
|
success: true,
|
|
message: "Berhasil mendapatkan diskusi",
|
|
data: {
|
|
id: data.id,
|
|
idDivision: data.idDivision,
|
|
division: data.Division.name,
|
|
isActive: data.isActive,
|
|
desc: data.desc,
|
|
status: data.status === 1 ? "Open" : "Close",
|
|
createdAt: data.createdAt,
|
|
createdBy: data.User.name,
|
|
komentar,
|
|
},
|
|
};
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan diskusi", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
params: t.Object({ id: t.String({ description: "ID diskusi divisi" }) }),
|
|
detail: { summary: "Detail Diskusi Divisi", tags: ["discussion"] },
|
|
})
|
|
|
|
// ─── DIVISION ────────────────────────────────────────────────────────────
|
|
|
|
.get("/division", async ({ query, set }) => {
|
|
try {
|
|
const { desa, group, search, page, active, get } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
let kondisi: any = {
|
|
isActive: active === "false" ? false : true,
|
|
idVillage: String(desa),
|
|
name: { contains: !search || search === "null" ? "" : search, mode: "insensitive" },
|
|
};
|
|
|
|
if (group && group !== "null") kondisi.idGroup = String(group);
|
|
|
|
const data = await prisma.division.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: kondisi,
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
desc: true,
|
|
idGroup: true,
|
|
Group: { select: { name: true } },
|
|
DivisionMember: { where: { isActive: true }, select: { idUser: true } },
|
|
},
|
|
orderBy: { createdAt: "desc" },
|
|
});
|
|
|
|
const result = data.map((v: any) => ({
|
|
..._.omit(v, ["DivisionMember", "Group"]),
|
|
group: v.Group.name,
|
|
jumlahMember: v.DivisionMember.length,
|
|
}));
|
|
|
|
return { success: true, message: "Berhasil mendapatkan divisi", data: result };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan divisi", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
group: t.Optional(t.String({ description: "ID group" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci nama" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
active: t.Optional(t.String({ description: "Filter aktif (true/false)" })),
|
|
}),
|
|
detail: { summary: "List Divisi", tags: ["division"] },
|
|
})
|
|
|
|
.get("/division/report", async ({ query, set }) => {
|
|
try {
|
|
const { desa, group, division, "date-start": date, "date-end": dateAkhir, cat } = query;
|
|
|
|
if (cat === "dokumen") {
|
|
let kondisi: any = {
|
|
isActive: true,
|
|
category: "FILE",
|
|
Division: { idVillage: String(desa) },
|
|
createdAt: { gte: new Date(String(date)), lte: new Date(String(dateAkhir)) },
|
|
};
|
|
|
|
if (group) kondisi.Division = { idGroup: String(group) };
|
|
if (division) kondisi.idDivision = String(division);
|
|
|
|
const dataDokumen = await prisma.divisionDocumentFolderFile.findMany({ where: kondisi });
|
|
const image = ["jpg", "jpeg", "png", "heic"];
|
|
let gambar = 0, dokumen = 0;
|
|
|
|
_.map(_.groupBy(dataDokumen, "extension"), (v: any) => {
|
|
if (image.includes(v[0].extension)) gambar += v.length;
|
|
else dokumen += v.length;
|
|
});
|
|
|
|
return { success: true, message: "Berhasil mendapatkan data", data: { gambar, dokumen } };
|
|
}
|
|
|
|
if (cat === "event") {
|
|
const baseWhere = (dateFilter: any) => group
|
|
? { isActive: true, Division: { idGroup: String(group) }, DivisionCalendarReminder: { some: dateFilter } }
|
|
: { isActive: true, Division: { idVillage: String(desa) }, DivisionCalendarReminder: { some: dateFilter } };
|
|
|
|
let selesaiWhere = baseWhere({ dateStart: { gte: new Date(String(date)), lte: new Date() } });
|
|
let comingWhere = baseWhere({ dateStart: { gt: new Date(), lte: new Date(String(dateAkhir)) } });
|
|
|
|
if (division) {
|
|
selesaiWhere = { ...selesaiWhere, idDivision: String(division) } as any;
|
|
comingWhere = { ...comingWhere, idDivision: String(division) } as any;
|
|
}
|
|
|
|
const [selesai, akan_datang] = await Promise.all([
|
|
prisma.divisionCalendar.count({ where: selesaiWhere }),
|
|
prisma.divisionCalendar.count({ where: comingWhere }),
|
|
]);
|
|
|
|
return { success: true, message: "Berhasil mendapatkan data", data: { selesai, akan_datang } };
|
|
}
|
|
|
|
// default: progress
|
|
let kondisiProgress: any = {
|
|
isActive: true,
|
|
Division: group ? { idGroup: String(group) } : { idVillage: String(desa) },
|
|
DivisionProjectTask: {
|
|
some: {
|
|
dateStart: { gte: new Date(String(date)) },
|
|
dateEnd: { lte: new Date(String(dateAkhir)) },
|
|
},
|
|
},
|
|
};
|
|
|
|
if (division) kondisiProgress.idDivision = String(division);
|
|
|
|
const data = await prisma.divisionProject.groupBy({ where: kondisiProgress, by: ["status"], _count: true });
|
|
const dataStatus = [
|
|
{ name: "Segera", status: 0 },
|
|
{ name: "Dikerjakan", status: 1 },
|
|
{ name: "Selesai", status: 2 },
|
|
{ name: "Dibatalkan", status: 3 },
|
|
];
|
|
|
|
const total = data.reduce((n, { _count }) => n + _count, 0);
|
|
const result = dataStatus.reduce((acc: any, ds) => {
|
|
const found = data.find((i: any) => i.status === ds.status);
|
|
const raw = found ? ((found._count * 100) / total).toFixed(2) : "0";
|
|
const fix = raw !== "100.00" ? (raw.slice(-2) === "00" ? raw.slice(0, 2) : raw) : "100";
|
|
acc[ds.name] = fix;
|
|
return acc;
|
|
}, {});
|
|
|
|
return { success: true, message: "Berhasil mendapatkan data", data: result };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan data", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
group: t.Optional(t.String({ description: "ID group" })),
|
|
division: t.Optional(t.String({ description: "ID divisi" })),
|
|
"date-start": t.Optional(t.String({ description: "Tanggal mulai (ISO)" })),
|
|
"date-end": t.Optional(t.String({ description: "Tanggal akhir (ISO)" })),
|
|
cat: t.Optional(t.String({ description: "Kategori: dokumen / event / (kosong = progress)" })),
|
|
}),
|
|
detail: { summary: "Laporan Divisi", tags: ["division"] },
|
|
})
|
|
|
|
.get("/division/:id", async ({ params, query, set }) => {
|
|
try {
|
|
const { id } = params;
|
|
const { desa } = query;
|
|
|
|
const data = await prisma.division.findUnique({
|
|
where: { id: String(id), idVillage: String(desa) },
|
|
});
|
|
|
|
if (!data) {
|
|
set.status = 404;
|
|
return { success: false, message: "Divisi tidak ditemukan" };
|
|
}
|
|
|
|
const memberRaw = await prisma.divisionMember.findMany({
|
|
where: { idDivision: String(id), isActive: true },
|
|
select: {
|
|
id: true,
|
|
isAdmin: true,
|
|
idUser: true,
|
|
User: { select: { name: true, img: true } },
|
|
},
|
|
orderBy: { isAdmin: "desc" },
|
|
});
|
|
|
|
const member = memberRaw.map((v: any) => ({
|
|
..._.omit(v, ["User"]),
|
|
name: v.User.name,
|
|
img: v.User.img,
|
|
}));
|
|
|
|
return { success: true, message: "Berhasil mendapatkan divisi", data: { ...data, member } };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan divisi", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
params: t.Object({ id: t.String({ description: "ID divisi" }) }),
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
}),
|
|
detail: { summary: "Detail Divisi", tags: ["division"] },
|
|
})
|
|
|
|
// ─── DOCUMENT ────────────────────────────────────────────────────────────
|
|
|
|
.get("/document", async ({ query, set }) => {
|
|
try {
|
|
const { division, desa, path, active, search, page, get } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
const pathFix = !path || path === "undefined" || path === "null" || path === "" ? "home" : path;
|
|
|
|
let kondisi: any = {
|
|
Division: { idVillage: String(desa) },
|
|
isActive: active === "false" ? false : true,
|
|
path: pathFix,
|
|
name: { contains: !search || search === "null" ? "" : search, mode: "insensitive" },
|
|
};
|
|
|
|
if (division && division !== "null") kondisi.idDivision = String(division);
|
|
|
|
let formatDataShare: any[] = [];
|
|
|
|
if (pathFix === "home") {
|
|
const dataShare = await prisma.divisionDocumentShare.findMany({
|
|
where: { isActive: true, idDivision: String(division), DivisionDocumentFolderFile: { isActive: true } },
|
|
select: {
|
|
DivisionDocumentFolderFile: {
|
|
select: {
|
|
idStorage: true,
|
|
id: true,
|
|
category: true,
|
|
name: true,
|
|
extension: true,
|
|
path: true,
|
|
User: { select: { name: true } },
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { DivisionDocumentFolderFile: { createdAt: "desc" } },
|
|
});
|
|
|
|
formatDataShare = dataShare.map((v: any) => ({
|
|
idStorage: v.DivisionDocumentFolderFile.idStorage,
|
|
id: v.DivisionDocumentFolderFile.id,
|
|
category: v.DivisionDocumentFolderFile.category,
|
|
name: v.DivisionDocumentFolderFile.name,
|
|
extension: v.DivisionDocumentFolderFile.extension,
|
|
path: v.DivisionDocumentFolderFile.path,
|
|
createdBy: v.DivisionDocumentFolderFile.User.name,
|
|
createdAt: v.DivisionDocumentFolderFile.createdAt,
|
|
updatedAt: v.DivisionDocumentFolderFile.updatedAt,
|
|
share: true,
|
|
}));
|
|
}
|
|
|
|
const data = await prisma.divisionDocumentFolderFile.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: kondisi,
|
|
select: {
|
|
id: true,
|
|
category: true,
|
|
name: true,
|
|
extension: true,
|
|
idStorage: true,
|
|
path: true,
|
|
User: { select: { name: true } },
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
},
|
|
orderBy: { createdAt: "desc" },
|
|
});
|
|
|
|
const allData = data.map((v: any) => ({
|
|
..._.omit(v, ["User"]),
|
|
createdBy: v.User.name,
|
|
share: false,
|
|
}));
|
|
|
|
if (formatDataShare.length > 0) allData.push(...formatDataShare);
|
|
|
|
return {
|
|
success: true,
|
|
message: "Berhasil mendapatkan item",
|
|
data: _.orderBy(allData, ["category", "createdAt"], ["desc", "desc"]),
|
|
};
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan item", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
division: t.Optional(t.String({ description: "ID divisi" })),
|
|
path: t.Optional(t.String({ description: "Path folder (default: home)" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci nama file" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
active: t.Optional(t.String({ description: "Filter aktif (true/false)" })),
|
|
}),
|
|
detail: { summary: "List Dokumen", tags: ["document"] },
|
|
})
|
|
|
|
// ─── GROUP ───────────────────────────────────────────────────────────────
|
|
|
|
.get("/group", async ({ query, set }) => {
|
|
try {
|
|
const { desa, active, search, page, get } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
const data = await prisma.group.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: {
|
|
isActive: active === "false" ? false : true,
|
|
idVillage: String(desa),
|
|
name: { contains: search ?? "", mode: "insensitive" },
|
|
},
|
|
orderBy: { name: "asc" },
|
|
});
|
|
|
|
return { success: true, message: "Berhasil mendapatkan grup", data };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan grup", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci nama" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
active: t.Optional(t.String({ description: "Filter aktif (true/false)" })),
|
|
}),
|
|
detail: { summary: "List Group", tags: ["group"] },
|
|
})
|
|
|
|
// ─── POSITION ────────────────────────────────────────────────────────────
|
|
|
|
.get("/position", async ({ query, set }) => {
|
|
try {
|
|
const { desa, group, active, search, page, get } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
let kondisi: any = {
|
|
isActive: active === "false" ? false : true,
|
|
Group: { idVillage: String(desa) },
|
|
name: { contains: search ?? "", mode: "insensitive" },
|
|
};
|
|
|
|
if (group && group !== "null") kondisi.idGroup = String(group);
|
|
|
|
const positions = await prisma.position.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: kondisi,
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
idGroup: true,
|
|
isActive: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
Group: { select: { name: true } },
|
|
},
|
|
orderBy: { name: "asc" },
|
|
});
|
|
|
|
const result = positions.map((v: any) => ({
|
|
..._.omit(v, ["Group"]),
|
|
group: v.Group.name,
|
|
}));
|
|
|
|
return { success: true, message: "Berhasil mendapatkan jabatan", data: result };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan jabatan", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
group: t.Optional(t.String({ description: "ID group" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci nama" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
active: t.Optional(t.String({ description: "Filter aktif (true/false)" })),
|
|
}),
|
|
detail: { summary: "List Jabatan", tags: ["position"] },
|
|
})
|
|
|
|
// ─── PROJECT ─────────────────────────────────────────────────────────────
|
|
|
|
.get("/project", async ({ query, set }) => {
|
|
try {
|
|
const { desa, search, status, group, page, get } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
let kondisi: any = {
|
|
isActive: true,
|
|
idVillage: String(desa),
|
|
title: { contains: !search || search === "null" ? "" : search, mode: "insensitive" },
|
|
};
|
|
|
|
if (status && status !== "null") {
|
|
const statusMap: Record<string, number> = { segera: 0, dikerjakan: 1, selesai: 2, batal: 3 };
|
|
kondisi.status = statusMap[status] ?? 0;
|
|
}
|
|
|
|
if (group && group !== "null") kondisi.idGroup = String(group);
|
|
|
|
const data = await prisma.project.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: kondisi,
|
|
select: {
|
|
id: true,
|
|
idGroup: true,
|
|
title: true,
|
|
desc: true,
|
|
status: true,
|
|
ProjectMember: { where: { isActive: true }, select: { idUser: true } },
|
|
ProjectTask: { where: { isActive: true }, select: { title: true, status: true } },
|
|
Group: { select: { name: true } },
|
|
},
|
|
orderBy: { createdAt: "desc" },
|
|
});
|
|
|
|
const result = data.map((v: any) => ({
|
|
..._.omit(v, ["ProjectMember", "ProjectTask", "status", "Group"]),
|
|
group: v.Group.name,
|
|
status: v.status === 1 ? "dikerjakan" : v.status === 2 ? "selesai" : v.status === 3 ? "batal" : "segera",
|
|
progress: _.ceil((v.ProjectTask.filter((i: any) => i.status === 1).length * 100) / v.ProjectTask.length),
|
|
member: v.ProjectMember.length,
|
|
}));
|
|
|
|
return { success: true, message: "Berhasil mendapatkan kegiatan", data: result };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan kegiatan", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
group: t.Optional(t.String({ description: "ID group" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci judul" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
status: t.Optional(t.String({ description: "Status: segera/dikerjakan/selesai/batal" })),
|
|
}),
|
|
detail: { summary: "List Kegiatan", tags: ["project"] },
|
|
})
|
|
|
|
.get("/project/:id", async ({ params, query, set }) => {
|
|
try {
|
|
const { id } = params;
|
|
const { cat } = query;
|
|
|
|
const data = await prisma.project.findUnique({
|
|
where: { id: String(id) },
|
|
select: {
|
|
id: true, idVillage: true, idGroup: true, title: true, status: true,
|
|
desc: true, reason: true, report: true, isActive: true,
|
|
Group: { select: { name: true } },
|
|
},
|
|
});
|
|
|
|
if (!data) {
|
|
set.status = 404;
|
|
return { success: false, message: "Kegiatan tidak ditemukan" };
|
|
}
|
|
|
|
if (cat === "data") {
|
|
const tasks = await prisma.projectTask.findMany({ where: { isActive: true, idProject: String(id) }, orderBy: { updatedAt: "desc" } });
|
|
const selesai = _.filter(tasks, { status: 1 }).length;
|
|
const progress = Math.ceil((selesai / tasks.length) * 100);
|
|
return {
|
|
success: true, message: "Berhasil mendapatkan kegiatan", data: {
|
|
id: data.id, idVillage: data.idVillage, idGroup: data.idGroup, group: data.Group.name,
|
|
title: data.title, status: data.status === 3 ? "batal" : data.status === 2 ? "selesai" : data.status === 1 ? "dikerjakan" : "segera",
|
|
desc: data.desc, reason: data.reason, report: data.report, isActive: data.isActive,
|
|
progress: _.isNaN(progress) ? 0 : progress,
|
|
},
|
|
};
|
|
}
|
|
|
|
if (cat === "task") {
|
|
const tasks = await prisma.projectTask.findMany({
|
|
where: { isActive: true, idProject: String(id) },
|
|
select: { id: true, title: true, desc: true, status: true, dateStart: true, dateEnd: true, createdAt: true },
|
|
orderBy: { createdAt: "asc" },
|
|
});
|
|
return {
|
|
success: true, message: "Berhasil mendapatkan kegiatan",
|
|
data: _.orderBy(tasks.map((v: any) => ({
|
|
..._.omit(v, ["dateStart", "dateEnd", "createdAt", "status"]),
|
|
status: v.status === 1 ? "selesai" : "belum selesai",
|
|
dateStart: moment(v.dateStart).format("DD-MM-YYYY"),
|
|
dateEnd: moment(v.dateEnd).format("DD-MM-YYYY"),
|
|
createdAt: moment(v.createdAt).format("DD-MM-YYYY HH:mm"),
|
|
})), "createdAt", "asc"),
|
|
};
|
|
}
|
|
|
|
if (cat === "file") {
|
|
const files = await prisma.projectFile.findMany({
|
|
where: { isActive: true, idProject: String(id) },
|
|
orderBy: { createdAt: "asc" },
|
|
select: { id: true, name: true, extension: true, idStorage: true },
|
|
});
|
|
return { success: true, message: "Berhasil mendapatkan kegiatan", data: files };
|
|
}
|
|
|
|
if (cat === "member") {
|
|
const members = await prisma.projectMember.findMany({
|
|
where: { isActive: true, idProject: String(id) },
|
|
select: { id: true, idUser: true, User: { select: { name: true, email: true, img: true, Position: { select: { name: true } } } } },
|
|
});
|
|
return {
|
|
success: true, message: "Berhasil mendapatkan kegiatan",
|
|
data: members.map((v: any) => ({
|
|
..._.omit(v, ["User"]),
|
|
name: v.User.name, email: v.User.email, img: v.User.img, position: v.User.Position.name,
|
|
})),
|
|
};
|
|
}
|
|
|
|
if (cat === "link") {
|
|
const links = await prisma.projectLink.findMany({ where: { isActive: true, idProject: String(id) }, orderBy: { createdAt: "asc" } });
|
|
return { success: true, message: "Berhasil mendapatkan kegiatan", data: links };
|
|
}
|
|
|
|
return { success: true, message: "Berhasil mendapatkan kegiatan", data: null };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan kegiatan", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
params: t.Object({ id: t.String({ description: "ID kegiatan" }) }),
|
|
query: t.Object({
|
|
cat: t.Optional(t.String({ description: "Kategori: data / task / file / member / link" })),
|
|
}),
|
|
detail: { summary: "Detail Kegiatan", tags: ["project"] },
|
|
})
|
|
|
|
// ─── TASK (DIVISI) ───────────────────────────────────────────────────────
|
|
|
|
.get("/task", async ({ query, set }) => {
|
|
try {
|
|
const { desa, division, search, status, page, get } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
let kondisi: any = {
|
|
isActive: true,
|
|
Division: { idVillage: String(desa) },
|
|
title: { contains: !search || search === "null" ? "" : search, mode: "insensitive" },
|
|
};
|
|
|
|
if (status && status !== "null") {
|
|
const statusMap: Record<string, number> = { segera: 0, dikerjakan: 1, selesai: 2, batal: 3 };
|
|
kondisi.status = statusMap[status] ?? 0;
|
|
}
|
|
|
|
if (division && division !== "null") kondisi.idDivision = String(division);
|
|
|
|
const data = await prisma.divisionProject.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: kondisi,
|
|
select: {
|
|
id: true,
|
|
idDivision: true,
|
|
title: true,
|
|
desc: true,
|
|
status: true,
|
|
DivisionProjectTask: { where: { isActive: true }, select: { title: true, status: true } },
|
|
DivisionProjectMember: { where: { isActive: true }, select: { idUser: true } },
|
|
Division: { select: { name: true } },
|
|
},
|
|
orderBy: { createdAt: "desc" },
|
|
});
|
|
|
|
const result = data.map((v: any) => ({
|
|
..._.omit(v, ["DivisionProjectTask", "DivisionProjectMember", "status", "Division"]),
|
|
division: v.Division.name,
|
|
status: v.status === 1 ? "dikerjakan" : v.status === 2 ? "selesai" : v.status === 3 ? "batal" : "segera",
|
|
progress: _.ceil((v.DivisionProjectTask.filter((i: any) => i.status === 1).length * 100) / v.DivisionProjectTask.length),
|
|
member: v.DivisionProjectMember.length,
|
|
}));
|
|
|
|
return { success: true, message: "Berhasil mendapatkan tugas divisi", data: result };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan tugas divisi", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
division: t.Optional(t.String({ description: "ID divisi" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci judul" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
status: t.Optional(t.String({ description: "Status: segera/dikerjakan/selesai/batal" })),
|
|
}),
|
|
detail: { summary: "List Tugas Divisi", tags: ["task"] },
|
|
})
|
|
|
|
.get("/task/:id", async ({ params, query, set }) => {
|
|
try {
|
|
const { id } = params;
|
|
const { cat } = query;
|
|
|
|
const data = await prisma.divisionProject.findUnique({
|
|
where: { id: String(id) },
|
|
select: {
|
|
id: true, idDivision: true, title: true, status: true,
|
|
desc: true, reason: true, report: true, isActive: true,
|
|
Division: { select: { name: true } },
|
|
},
|
|
});
|
|
|
|
if (!data) {
|
|
set.status = 404;
|
|
return { success: false, message: "Tugas tidak ditemukan" };
|
|
}
|
|
|
|
if (cat === "data") {
|
|
const tasks = await prisma.divisionProjectTask.findMany({ where: { isActive: true, idProject: String(id) }, orderBy: { updatedAt: "desc" } });
|
|
const selesai = _.filter(tasks, { status: 1 }).length;
|
|
const progress = Math.ceil((selesai / tasks.length) * 100);
|
|
return {
|
|
success: true, message: "Berhasil mendapatkan tugas divisi", data: {
|
|
id: data.id, idDivision: data.idDivision, division: data.Division.name,
|
|
title: data.title, status: data.status === 3 ? "batal" : data.status === 2 ? "selesai" : data.status === 1 ? "dikerjakan" : "segera",
|
|
desc: data.desc, reason: data.reason, report: data.report, isActive: data.isActive, progress,
|
|
},
|
|
};
|
|
}
|
|
|
|
if (cat === "task") {
|
|
const tasks = await prisma.divisionProjectTask.findMany({
|
|
where: { isActive: true, idProject: String(id) },
|
|
select: { id: true, title: true, status: true, dateStart: true, dateEnd: true },
|
|
orderBy: { createdAt: "asc" },
|
|
});
|
|
return {
|
|
success: true, message: "Berhasil mendapatkan tugas divisi",
|
|
data: tasks.map((v: any) => ({
|
|
..._.omit(v, ["dateStart", "dateEnd", "status"]),
|
|
status: v.status === 1 ? "selesai" : "belum selesai",
|
|
dateStart: moment(v.dateStart).format("DD-MM-YYYY"),
|
|
dateEnd: moment(v.dateEnd).format("DD-MM-YYYY"),
|
|
})),
|
|
};
|
|
}
|
|
|
|
if (cat === "file") {
|
|
const files = await prisma.divisionProjectFile.findMany({
|
|
where: { isActive: true, idProject: String(id) },
|
|
select: { id: true, ContainerFileDivision: { select: { id: true, name: true, extension: true, idStorage: true } } },
|
|
});
|
|
return {
|
|
success: true, message: "Berhasil mendapatkan tugas divisi",
|
|
data: files.map((v: any) => ({
|
|
..._.omit(v, ["ContainerFileDivision"]),
|
|
nameInStorage: v.ContainerFileDivision.id,
|
|
name: v.ContainerFileDivision.name,
|
|
extension: v.ContainerFileDivision.extension,
|
|
idStorage: v.ContainerFileDivision.idStorage,
|
|
})),
|
|
};
|
|
}
|
|
|
|
if (cat === "member") {
|
|
const members = await prisma.divisionProjectMember.findMany({
|
|
where: { isActive: true, idProject: String(id) },
|
|
select: { id: true, idUser: true, User: { select: { name: true, email: true, img: true, Position: { select: { name: true } } } } },
|
|
});
|
|
return {
|
|
success: true, message: "Berhasil mendapatkan tugas divisi",
|
|
data: members.map((v: any) => ({
|
|
..._.omit(v, ["User"]),
|
|
name: v.User.name, email: v.User.email, img: v.User.img, position: v.User.Position.name,
|
|
})),
|
|
};
|
|
}
|
|
|
|
if (cat === "link") {
|
|
const links = await prisma.divisionProjectLink.findMany({ where: { isActive: true, idProject: String(id) }, orderBy: { createdAt: "asc" } });
|
|
return { success: true, message: "Berhasil mendapatkan tugas divisi", data: links };
|
|
}
|
|
|
|
return { success: true, message: "Berhasil mendapatkan tugas divisi", data: null };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan tugas divisi", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
params: t.Object({ id: t.String({ description: "ID tugas divisi" }) }),
|
|
query: t.Object({
|
|
cat: t.Optional(t.String({ description: "Kategori: data / task / file / member / link" })),
|
|
}),
|
|
detail: { summary: "Detail Tugas Divisi", tags: ["task"] },
|
|
})
|
|
|
|
// ─── USER ────────────────────────────────────────────────────────────────
|
|
|
|
.get("/user", async ({ query, set }) => {
|
|
try {
|
|
const { search, desa, group, active, page, get } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
let kondisi: any = {
|
|
isActive: active === "false" ? false : true,
|
|
idVillage: String(desa),
|
|
name: { contains: search ?? "", mode: "insensitive" },
|
|
NOT: { idUserRole: "developer" },
|
|
};
|
|
|
|
if (group && group !== "null") kondisi.idGroup = String(group);
|
|
|
|
const users = await prisma.user.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: kondisi,
|
|
select: {
|
|
id: true,
|
|
idUserRole: true,
|
|
isActive: true,
|
|
nik: true,
|
|
name: true,
|
|
phone: true,
|
|
Position: { select: { name: true } },
|
|
Group: { select: { name: true } },
|
|
},
|
|
orderBy: { name: "asc" },
|
|
});
|
|
|
|
const result = users.map((v: any) => ({
|
|
..._.omit(v, ["phone", "gender", "Group", "Position"]),
|
|
gender: v.gender === "F" ? "Perempuan" : "Laki-Laki",
|
|
phone: "+" + v.phone,
|
|
group: v.Group.name,
|
|
position: v?.Position?.name,
|
|
}));
|
|
|
|
return { success: true, message: "Berhasil mendapatkan member", data: result };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan anggota", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
desa: t.Optional(t.String({ description: "ID desa" })),
|
|
group: t.Optional(t.String({ description: "ID group" })),
|
|
search: t.Optional(t.String({ description: "Kata kunci nama" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
active: t.Optional(t.String({ description: "Filter aktif (true/false)" })),
|
|
}),
|
|
detail: { summary: "List User", tags: ["user"] },
|
|
})
|
|
|
|
.get("/user/:id", async ({ params, set }) => {
|
|
try {
|
|
const { id } = params;
|
|
|
|
const user = await prisma.user.findUnique({
|
|
where: { id },
|
|
select: {
|
|
id: true, nik: true, name: true, phone: true, email: true,
|
|
gender: true, img: true, idGroup: true, isActive: true,
|
|
idPosition: true, createdAt: true, updatedAt: true,
|
|
UserRole: { select: { name: true, id: true } },
|
|
Position: { select: { name: true, id: true } },
|
|
Group: { select: { name: true, id: true } },
|
|
},
|
|
});
|
|
|
|
if (!user) {
|
|
set.status = 404;
|
|
return { success: false, message: "User tidak ditemukan" };
|
|
}
|
|
|
|
const result = _.omit(
|
|
{
|
|
...user,
|
|
gender: user.gender === "F" ? "Perempuan" : "Laki-Laki",
|
|
phone: "+62" + user.phone,
|
|
group: user.Group.name,
|
|
position: user.Position?.name,
|
|
idUserRole: user.UserRole.id,
|
|
role: user.UserRole.name,
|
|
},
|
|
["Group", "Position", "UserRole"],
|
|
);
|
|
|
|
return { success: true, message: "Berhasil mendapatkan anggota", data: result };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan anggota", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
params: t.Object({ id: t.String({ description: "ID user" }) }),
|
|
detail: { summary: "Detail User", tags: ["user"] },
|
|
})
|
|
|
|
// ─── VILLAGE ─────────────────────────────────────────────────────────────
|
|
|
|
.get("/village", async ({ query, set }) => {
|
|
try {
|
|
const { active, search, page, get } = query;
|
|
|
|
const getFix = !get || _.isNaN(Number(get)) ? 10 : Number(get);
|
|
const dataSkip = !page ? 0 : Number(page) * getFix - getFix;
|
|
|
|
const data = await prisma.village.findMany({
|
|
skip: dataSkip,
|
|
take: getFix,
|
|
where: {
|
|
isActive: active === "false" ? false : true,
|
|
name: { contains: search ?? "", mode: "insensitive" },
|
|
},
|
|
select: { id: true, name: true, isActive: true, createdAt: true, updatedAt: true },
|
|
orderBy: { name: "asc" },
|
|
});
|
|
|
|
return { success: true, message: "Berhasil mendapatkan desa", data };
|
|
} catch (error) {
|
|
set.status = 500;
|
|
return { success: false, message: "Gagal mendapatkan desa", reason: (error as Error).message };
|
|
}
|
|
}, {
|
|
query: t.Object({
|
|
search: t.Optional(t.String({ description: "Kata kunci nama desa" })),
|
|
page: t.Optional(t.String({ description: "Halaman" })),
|
|
get: t.Optional(t.String({ description: "Jumlah data per halaman" })),
|
|
active: t.Optional(t.String({ description: "Filter aktif (true/false)" })),
|
|
}),
|
|
detail: { summary: "List Desa", tags: ["village"] },
|
|
});
|
|
|
|
export const GET = AiServer.handle;
|
|
export const OPTIONS = AiServer.handle;
|