From 648f23c760cb7be7eb3f28fc58b6103445f0b561 Mon Sep 17 00:00:00 2001 From: amal Date: Mon, 15 Sep 2025 17:38:13 +0800 Subject: [PATCH 01/21] upd: api ai Deskripsi: - api ai list pengumuman - api ai detail pengumuman - api ai list banner : blm selesai No Issues --- src/app/api/ai/announcement/[id]/route.ts | 95 +++++++++++++++++++++++ src/app/api/ai/announcement/route.ts | 54 +++++++++++++ src/app/api/ai/banner/route.ts | 39 ++++++++++ src/app/api/version-app/route.ts | 2 +- 4 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 src/app/api/ai/announcement/[id]/route.ts create mode 100644 src/app/api/ai/announcement/route.ts create mode 100644 src/app/api/ai/banner/route.ts diff --git a/src/app/api/ai/announcement/[id]/route.ts b/src/app/api/ai/announcement/[id]/route.ts new file mode 100644 index 0000000..e301cc9 --- /dev/null +++ b/src/app/api/ai/announcement/[id]/route.ts @@ -0,0 +1,95 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + + +// GET ONE PENGUMUMAN, UNTUK TAMPIL DETAIL PENGUMUMAN +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + + const data = await prisma.announcement.count({ + where: { + id: id, + }, + }); + + if (data == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan pengumuman, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const announcement = await prisma.announcement.findUnique({ + where: { + id: id, + }, + select: { + id: true, + title: true, + desc: true, + }, + }); + + if (!announcement) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan pengumuman, data tidak ditemukan", + }, + { status: 404 } + ); + } + + let dataFix = { ...announcement, member: {} }; + + const announcementMember = await prisma.announcementMember.findMany({ + where: { + idAnnouncement: id, + }, + select: { + idGroup: true, + idDivision: true, + Group: { + select: { + name: true, + }, + }, + Division: { + select: { + name: true, + }, + }, + }, + }); + + const formatMember = announcementMember.map((v: any) => ({ + ..._.omit(v, ["Group", "Division"]), + idGroup: v.idGroup, + idDivision: v.idDivision, + group: v.Group.name, + division: v.Division.name + })) + + dataFix.member = formatMember + + return NextResponse.json( + { + success: true, + message: "Berhasil mendapatkan pengumuman", + data: dataFix, + }, + { status: 200 } + ); + + + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan pengumuman, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/ai/announcement/route.ts b/src/app/api/ai/announcement/route.ts new file mode 100644 index 0000000..15b27db --- /dev/null +++ b/src/app/api/ai/announcement/route.ts @@ -0,0 +1,54 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import moment from "moment"; +import "moment/locale/id"; +import { NextResponse } from "next/server"; +export const dynamic = 'force-dynamic' + + + +// GET ALL PENGUMUMAN +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const judul = searchParams.get('q'); + const page = searchParams.get('p'); + const get = searchParams.get('get'); + const villageId = searchParams.get('desa'); + const dataSkip = page == null || page == undefined ? 0 : Number(page) * 10 - 10; + + let kondisi: any = { + idVillage: String(villageId), + isActive: true, + title: { + contains: (judul == undefined || judul == null) ? "" : judul, + mode: "insensitive" + } + } + + const announcements = await prisma.announcement.findMany({ + skip: dataSkip, + take: (get == null || get == undefined || get == "" || _.isNaN(Number(get))) ? 10 : Number(get), + where: kondisi, + select: { + id: true, + title: true, + desc: true, + createdAt: true, + }, + orderBy: { + createdAt: 'desc' + } + }); + + const allData = announcements.map((v: any) => ({ + ..._.omit(v, ["createdAt"]), + createdAt: moment(v.createdAt).format("ll") + })) + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan pengumuman", data: allData, }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan pengumuman, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/ai/banner/route.ts b/src/app/api/ai/banner/route.ts new file mode 100644 index 0000000..ccbefea --- /dev/null +++ b/src/app/api/ai/banner/route.ts @@ -0,0 +1,39 @@ +import { prisma } from "@/module/_global"; +import { NextResponse } from "next/server"; + + +// GET ALL BANNER +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const judul = searchParams.get('q'); + const page = searchParams.get('p'); + const get = searchParams.get('get'); + const villageId = searchParams.get('desa'); + const dataSkip = page == null || page == undefined ? 0 : Number(page) * 10 - 10; + + let kondisi: any = { + idVillage: String(villageId), + isActive: true, + title: { + contains: (judul == undefined || judul == null) ? "" : judul, + mode: "insensitive" + } + } + + const data = await prisma.bannerImage.findMany({ + where: { + isActive: true, + idVillage: String(villageId) + }, + orderBy: { + createdAt: 'desc' + } + }); + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan banner", data }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan data banner, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/version-app/route.ts b/src/app/api/version-app/route.ts index 92ada2d..19f756a 100644 --- a/src/app/api/version-app/route.ts +++ b/src/app/api/version-app/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; export async function GET(request: Request) { try { - return NextResponse.json({ success: true, version: "1.8.0", tahap: "beta", update: "-api mobile; -login tanpa otp (mobile app); -tambah laporan pada project dan tugas divisi; -tambah upload link pada project dan tugas divisi; -tambah detail tanggal dan jam pada project dan tugas divisi" }, { status: 200 }); + return NextResponse.json({ success: true, version: "1.9.0", tahap: "beta", update: "-api mobile; -login tanpa otp (mobile app); -tambah laporan pada project dan tugas divisi; -tambah upload link pada project dan tugas divisi; -tambah detail tanggal dan jam pada project dan tugas divisi; -api jenna ai" }, { status: 200 }); } catch (error) { console.error(error); return NextResponse.json({ success: false, version: "Gagal mendapatkan version, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); From 83085418eda0b54ef69de139b89e93714950cbcb Mon Sep 17 00:00:00 2001 From: amal Date: Tue, 16 Sep 2025 14:23:15 +0800 Subject: [PATCH 02/21] upd: pengumuman deskripsiL : - update api ai pengumuman No Issues --- src/app/api/ai/announcement/route.ts | 36 +++++++++++++--------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/app/api/ai/announcement/route.ts b/src/app/api/ai/announcement/route.ts index 15b27db..a4a1d4a 100644 --- a/src/app/api/ai/announcement/route.ts +++ b/src/app/api/ai/announcement/route.ts @@ -1,6 +1,5 @@ import { prisma } from "@/module/_global"; import _ from "lodash"; -import moment from "moment"; import "moment/locale/id"; import { NextResponse } from "next/server"; export const dynamic = 'force-dynamic' @@ -11,42 +10,41 @@ export const dynamic = 'force-dynamic' export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); - const judul = searchParams.get('q'); - const page = searchParams.get('p'); + const judul = searchParams.get('search'); + const page = searchParams.get('page'); const get = searchParams.get('get'); const villageId = searchParams.get('desa'); - const dataSkip = page == null || page == undefined ? 0 : Number(page) * 10 - 10; + const active = searchParams.get('active'); + + let getFix = 0; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; + let kondisi: any = { idVillage: String(villageId), - isActive: true, + isActive: (active == "false" || active == undefined) ? false : true, title: { contains: (judul == undefined || judul == null) ? "" : judul, mode: "insensitive" } } - const announcements = await prisma.announcement.findMany({ + const data = await prisma.announcement.findMany({ skip: dataSkip, - take: (get == null || get == undefined || get == "" || _.isNaN(Number(get))) ? 10 : Number(get), + take: getFix, where: kondisi, - select: { - id: true, - title: true, - desc: true, - createdAt: true, - }, orderBy: { createdAt: 'desc' } }); - const allData = announcements.map((v: any) => ({ - ..._.omit(v, ["createdAt"]), - createdAt: moment(v.createdAt).format("ll") - })) - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan pengumuman", data: allData, }, { status: 200 }); + return NextResponse.json({ success: true, message: "Berhasil mendapatkan pengumuman", data, }, { status: 200 }); } catch (error) { console.error(error); return NextResponse.json({ success: false, message: "Gagal mendapatkan pengumuman, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); From f87b99f70424a933e517c33510e10f99bb69575c Mon Sep 17 00:00:00 2001 From: amal Date: Tue, 16 Sep 2025 14:24:58 +0800 Subject: [PATCH 03/21] upd: api ai Deskripsi : - update banner api ai No Issues --- src/app/api/ai/banner/[id]/route.ts | 21 +++++++++++++++++++++ src/app/api/ai/banner/route.ts | 25 +++++++++++++++++-------- 2 files changed, 38 insertions(+), 8 deletions(-) create mode 100644 src/app/api/ai/banner/[id]/route.ts diff --git a/src/app/api/ai/banner/[id]/route.ts b/src/app/api/ai/banner/[id]/route.ts new file mode 100644 index 0000000..01f91ac --- /dev/null +++ b/src/app/api/ai/banner/[id]/route.ts @@ -0,0 +1,21 @@ +import { prisma } from "@/module/_global"; +import { NextResponse } from "next/server"; + + +// GET ONE BANNER +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + + const data = await prisma.bannerImage.findUnique({ + where: { + id: String(id) + } + }) + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan banner", data }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan banner, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/ai/banner/route.ts b/src/app/api/ai/banner/route.ts index ccbefea..93704b4 100644 --- a/src/app/api/ai/banner/route.ts +++ b/src/app/api/ai/banner/route.ts @@ -1,4 +1,5 @@ import { prisma } from "@/module/_global"; +import _ from "lodash"; import { NextResponse } from "next/server"; @@ -6,15 +7,24 @@ import { NextResponse } from "next/server"; export async function GET(request: Request) { try { const { searchParams } = new URL(request.url); - const judul = searchParams.get('q'); - const page = searchParams.get('p'); + const judul = searchParams.get('search'); + const page = searchParams.get('page'); const get = searchParams.get('get'); const villageId = searchParams.get('desa'); - const dataSkip = page == null || page == undefined ? 0 : Number(page) * 10 - 10; + const active = searchParams.get('active'); + + let getFix = 0; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; let kondisi: any = { idVillage: String(villageId), - isActive: true, + isActive: (active == "false" || active == undefined) ? false : true, title: { contains: (judul == undefined || judul == null) ? "" : judul, mode: "insensitive" @@ -22,10 +32,9 @@ export async function GET(request: Request) { } const data = await prisma.bannerImage.findMany({ - where: { - isActive: true, - idVillage: String(villageId) - }, + skip: dataSkip, + take: getFix, + where: kondisi, orderBy: { createdAt: 'desc' } From 2e4eaaec9537c7833f76c1463b93c9273398865b Mon Sep 17 00:00:00 2001 From: amal Date: Tue, 16 Sep 2025 15:35:47 +0800 Subject: [PATCH 04/21] api ai Deskripsi: - api get list calendar - api get detail calendar No Issues --- src/app/api/ai/calendar/[id]/route.ts | 99 +++++++++++++++++ src/app/api/ai/calendar/route.ts | 149 ++++++++++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 src/app/api/ai/calendar/[id]/route.ts create mode 100644 src/app/api/ai/calendar/route.ts diff --git a/src/app/api/ai/calendar/[id]/route.ts b/src/app/api/ai/calendar/[id]/route.ts new file mode 100644 index 0000000..9d43c90 --- /dev/null +++ b/src/app/api/ai/calendar/[id]/route.ts @@ -0,0 +1,99 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; + +// GET ONE CALENDER BY ID KALENDER REMINDER +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params + + const cek = await prisma.divisionCalendarReminder.count({ + where: { + id: id + } + }) + + if (cek == 0) { + return NextResponse.json( + { + success: false, + message: "Gagal mendapatkan acara, data tidak ditemukan", + }, + { status: 404 } + ); + } + + const data: any = await prisma.divisionCalendarReminder.findUnique({ + where: { + id: 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, ...dataCalender } = data + const timeStart = moment.utc(dataCalender?.timeStart).format("HH:mm") + const timeEnd = moment.utc(dataCalender?.timeEnd).format("HH:mm") + const idCalendar = data?.DivisionCalendar.id + const title = data?.DivisionCalendar?.title + const desc = data?.DivisionCalendar?.desc + const linkMeet = data?.DivisionCalendar?.linkMeet + const repeatEventTyper = data?.DivisionCalendar?.repeatEventTyper + const repeatValue = data?.DivisionCalendar?.repeatValue + + + const result = { ...dataCalender, timeStart, timeEnd, title, desc, linkMeet, repeatEventTyper, repeatValue } + + + const member = await prisma.divisionCalendarMember.findMany({ + where: { + idCalendar + }, + select: { + id: true, + idUser: true, + User: { + select: { + id: true, + name: true, + email: true, + img: true + } + } + } + }) + const fixMember = member.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + email: v.User.email, + img: v.User.img + })) + + + const dataFix = { + ...result, + member: fixMember, + } + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan kalender", data: dataFix }, { status: 200 }); + + } catch (error) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan kalender, data tidak ditemukan (error: 500)", }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/ai/calendar/route.ts b/src/app/api/ai/calendar/route.ts new file mode 100644 index 0000000..c07d011 --- /dev/null +++ b/src/app/api/ai/calendar/route.ts @@ -0,0 +1,149 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import moment from "moment"; +import "moment/locale/id"; +import { NextResponse } from "next/server"; + +//GET ALL CALENDER +export async function GET(request: Request) { + try { + + const { searchParams } = new URL(request.url); + const idDivision = searchParams.get("division"); + const isDate = searchParams.get("date") + const villageId = searchParams.get("desa") + const active = searchParams.get("active") + const search = searchParams.get("search") + const page = searchParams.get("page") + const get = searchParams.get("get") + + let getFix = 0; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; + + + let kondisi: any = {} + + if (idDivision != "" && idDivision != null && idDivision != undefined) { + if (isDate != null && isDate != undefined && isDate != "") { + kondisi = { + idDivision: String(idDivision), + dateStart: new Date(String(isDate)), + DivisionCalendar: { + title: { + contains: (search == undefined || search == null) ? "" : search, + mode: "insensitive" + }, + isActive: (active == "false" || active == undefined) ? false : true, + Division: { + idVillage: String(villageId) + } + } + } + } else { + kondisi = { + idDivision: String(idDivision), + DivisionCalendar: { + title: { + contains: (search == undefined || search == null) ? "" : search, + mode: "insensitive" + }, + isActive: (active == "false" || active == undefined) ? false : true, + Division: { + idVillage: String(villageId) + } + } + } + } + } else { + if (isDate != null && isDate != undefined && isDate != "") { + kondisi = { + dateStart: new Date(String(isDate)), + DivisionCalendar: { + title: { + contains: (search == undefined || search == null) ? "" : search, + mode: "insensitive" + }, + isActive: (active == "false" || active == undefined) ? false : true, + Division: { + idVillage: String(villageId) + } + } + } + } else { + kondisi = { + DivisionCalendar: { + title: { + contains: (search == undefined || search == null) ? "" : search, + mode: "insensitive" + }, + isActive: (active == "false" || active == undefined) ? false : true, + Division: { + idVillage: String(villageId) + } + } + } + } + } + + 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 allOmit = data.map((v: any) => ({ + ..._.omit(v, ["DivisionCalendar", "User"]), + 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 NextResponse.json({ success: true, message: "Berhasil mendapatkan kalender", data: allOmit }, { status: 200 }); + + } catch (error) { + console.error(error) + return NextResponse.json({ success: false, message: "Gagal mendapatkan kalender, data tidak ditemukan (error: 500)" }, { status: 404 }); + } +} \ No newline at end of file From f10b87f0f264bbae4a790e9ecb2596cce7882421 Mon Sep 17 00:00:00 2001 From: amal Date: Tue, 16 Sep 2025 17:10:03 +0800 Subject: [PATCH 05/21] upd : api ai diskusi divisi Deskripsi: - list diskusi divisi - detail diskusi divisi No Issues" git status --- src/app/api/ai/discussion/[id]/route.ts | 92 ++++++++++++++++++++++++ src/app/api/ai/discussion/route.ts | 95 +++++++++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 src/app/api/ai/discussion/[id]/route.ts create mode 100644 src/app/api/ai/discussion/route.ts diff --git a/src/app/api/ai/discussion/[id]/route.ts b/src/app/api/ai/discussion/[id]/route.ts new file mode 100644 index 0000000..c7d9876 --- /dev/null +++ b/src/app/api/ai/discussion/[id]/route.ts @@ -0,0 +1,92 @@ +import { prisma } from "@/module/_global"; +import { NextResponse } from "next/server"; + +// GET ONE DISCUSSION BY ID +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params + + const cek = await prisma.divisionDisscussion.count({ + where: { id } + }) + + if (cek == 0) { + return NextResponse.json( + { success: false, message: "Gagal mendapatkan diskusi, data tidak ditemukan" }, + { status: 404 } + ); + } + + 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, img: true } }, + DivisionDisscussionComment: { + select: { + id: true, + comment: true, + createdAt: true, + User: { select: { name: true, img: true } } + } + }, + } + }); + + if (!data) { + return NextResponse.json( + { success: false, message: "Diskusi tidak ditemukan" }, + { status: 404 } + ); + } + + // ambil nama creator + const createdBy = data.User.name; + const status = data.status == 1 ? "Open" : "Close" + const division = data.Division.name + + // mapping komentar → hilangkan nested User + const komentar = data.DivisionDisscussionComment.map((comment: any) => ({ + id: comment.id, + comment: comment.comment, + createdAt: comment.createdAt, + username: comment.User.name, + userimg: comment.User.img, + })); + + // bentuk hasil akhir sesuai request + const result = { + id: data.id, + idDivision: data.idDivision, + division, + isActive: data.isActive, + desc: data.desc, + status, + createdAt: data.createdAt, + createdBy, + komentar, + }; + + return NextResponse.json( + { success: true, message: "Berhasil mendapatkan diskusi", data: result }, + { status: 200 } + ); + + } catch (error) { + console.error(error); + return NextResponse.json( + { success: false, message: "Gagal mendapatkan diskusi, coba lagi nanti (error: 500)", reason: (error as Error).message }, + { status: 500 } + ); + } +} diff --git a/src/app/api/ai/discussion/route.ts b/src/app/api/ai/discussion/route.ts new file mode 100644 index 0000000..a37f0b9 --- /dev/null +++ b/src/app/api/ai/discussion/route.ts @@ -0,0 +1,95 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import "moment/locale/id"; +import { NextResponse } from "next/server"; + + +// GET ALL DISCUSSION DIVISION ACTIVE = TRUE +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const idDivision = searchParams.get("division"); + const search = searchParams.get('search'); + const page = searchParams.get('page'); + const status = searchParams.get('status'); + const isActive = searchParams.get('active'); + const villageId = searchParams.get('desa'); + const get = searchParams.get('get'); + + let getFix = 0; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: isActive == "false" ? false : true, + status: status == "close" ? 2 : 1, + Division: { + idVillage: String(villageId) + }, + desc: { + contains: (search == undefined || search == "null") ? "" : search, + mode: "insensitive" + }, + } + + + if (idDivision != "null" && idDivision != null && idDivision != undefined) { + kondisi = { + isActive: isActive == "false" ? false : true, + status: status == "close" ? 2 : 1, + idDivision: idDivision, + Division: { + idVillage: String(villageId) + }, + desc: { + contains: (search == undefined || search == "null") ? "" : search, + mode: "insensitive" + }, + } + } + + 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 fixData = data.map((v: any) => ({ + ..._.omit(v, ["DivisionDisscussionComment", "status", "Division"]), + totalKomentar: v.DivisionDisscussionComment.length, + status: v.status == 1 ? "Open" : "Close", + division: v.Division.name + })) + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan diskusi", data: fixData, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file From bef4dd1969c3c64cdde43f9f8ad1c0a5df5bf64e Mon Sep 17 00:00:00 2001 From: amal Date: Tue, 16 Sep 2025 17:31:46 +0800 Subject: [PATCH 06/21] upd: api ai Deskripsi: - api ai get list discussion general - api ai detail discussion general nb : belm selesai No Issues --- .../api/ai/discussion-general/[id]/route.ts | 111 ++++++++++++++++++ src/app/api/ai/discussion-general/route.ts | 92 +++++++++++++++ 2 files changed, 203 insertions(+) create mode 100644 src/app/api/ai/discussion-general/[id]/route.ts create mode 100644 src/app/api/ai/discussion-general/route.ts diff --git a/src/app/api/ai/discussion-general/[id]/route.ts b/src/app/api/ai/discussion-general/[id]/route.ts new file mode 100644 index 0000000..3df7047 --- /dev/null +++ b/src/app/api/ai/discussion-general/[id]/route.ts @@ -0,0 +1,111 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import moment from "moment"; +import "moment/locale/id"; +import { NextResponse } from "next/server"; + + +// GET ONE DETAIL DISKUSI UMUM +export async function GET(request: Request, context: { params: { id: string } }) { + try { + let dataFix + const { id } = context.params + const { searchParams } = new URL(request.url); + const kategori = searchParams.get("cat"); + + const cek = await prisma.discussion.count({ + where: { + id, + } + }) + + if (cek == 0) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, data tidak ditemukan" }, { status: 404 }); + } + + if (kategori == "detail") { + const data = await prisma.discussion.findUnique({ + where: { + id, + }, + select: { + isActive: true, + id: true, + title: true, + idGroup: true, + desc: true, + status: true, + createdAt: true, + } + }) + + dataFix = { + id: data?.id, + isActive: data?.isActive, + idGroup: data?.idGroup, + title: data?.title, + desc: data?.desc, + status: data?.status, + createdAt: moment(data?.createdAt).format("ll"), + } + + } else if (kategori == "komentar") { + 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 + } + } + } + }) + + dataFix = data.map((v: any) => ({ + ..._.omit(v, ["createdAt", "User",]), + createdAt: moment(v.createdAt).format("lll").replace('pukul', ''), + username: v.User.name, + img: v.User.img + })) + + } else if (kategori == "anggota") { + const data = await prisma.discussionMember.findMany({ + where: { + idDiscussion: id, + isActive: true + }, + select: { + idUser: true, + User: { + select: { + name: true, + img: true + } + } + } + }) + + dataFix = data.map((v: any) => ({ + ..._.omit(v, ["User",]), + name: v.User.name, + img: v.User.img + })) + } + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan diskusi", data: dataFix }, { status: 200 }); + + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} diff --git a/src/app/api/ai/discussion-general/route.ts b/src/app/api/ai/discussion-general/route.ts new file mode 100644 index 0000000..1e26ea7 --- /dev/null +++ b/src/app/api/ai/discussion-general/route.ts @@ -0,0 +1,92 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import "moment/locale/id"; +import { NextResponse } from "next/server"; + + +// GET ALL DISCUSSION GENERAL +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const idGroup = searchParams.get("group"); + const idVillage = searchParams.get("desa"); + const search = searchParams.get('search'); + const page = searchParams.get('page'); + const status = searchParams.get('status'); + const active = searchParams.get('active'); + const get = searchParams.get('get') + + + let getFix = 10; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: active == "false" ? false : true, + status: status == "close" ? 2 : 1, + idVillage: String(idVillage), + title: { + contains: (search == undefined || search == "null") ? "" : search, + mode: "insensitive" + }, + } + + if (idGroup != "null" && idGroup != undefined && idGroup != "") { + kondisi = { + ...kondisi, + idGroup: String(idGroup) + } + } + + + 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 fixData = data.map((v: any) => ({ + ..._.omit(v, ["DiscussionComment", "status", "Group"]), + totalKomentar: v.DiscussionComment.length, + status: v.status == 1 ? "Open" : "Close", + group: v.Group.name, + })) + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan diskusi", data: fixData }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} From b78013c160a1d33c58eb3b5e1abf44f39748a3a6 Mon Sep 17 00:00:00 2001 From: amal Date: Tue, 16 Sep 2025 17:32:55 +0800 Subject: [PATCH 07/21] upd: api ai swager --- darmasaba-api-ai.yml | 1472 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1472 insertions(+) create mode 100644 darmasaba-api-ai.yml diff --git a/darmasaba-api-ai.yml b/darmasaba-api-ai.yml new file mode 100644 index 0000000..9ae1e25 --- /dev/null +++ b/darmasaba-api-ai.yml @@ -0,0 +1,1472 @@ +openapi: 3.0.3 +info: + title: API AI Desa+ + description: API untuk AI Desa+ + version: 1.0.0 + contact: + name: API Support + email: support@desa-plus.com + +servers: + - url: http://localhost:3000/api/ai + description: Production server + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + + schemas: + # Common Schemas + User: + type: object + properties: + id: + type: string + name: + type: string + phone: + type: string + # Tambahkan properties lain sesuai kebutuhan + required: + - id + + Error: + type: object + properties: + message: + type: string + status: + type: integer + + + + + # Banner + BannerBase: + type: object + properties: + id: + type: string + idVillage: + type: string + title: + type: string + extension: + type: string + image: + type: string + isActive: + type: boolean + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + + BannerListResponse: + type: object + properties: + success: + type: boolean + message: + type: string + data: + type: array + items: + $ref: '#/components/schemas/BannerBase' + + BannerDetailResponse: + type: object + properties: + success: + type: boolean + message: + type: string + data: + $ref: '#/components/schemas/BannerBase' + + + # Announcement + AnnouncementBase: + type: object + properties: + id: + type: string + idVillage: + type: string + title: + type: string + desc: + type: string + isActive: + type: boolean + createdBy: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + + AnnouncementMember: + type: object + properties: + idGroup: + type: string + idDivision: + type: string + group: + type: string + division: + type: string + + AnnouncementListResponse: + type: object + properties: + success: + type: boolean + message: + type: string + data: + type: array + items: + $ref: '#/components/schemas/AnnouncementBase' + + AnnouncementDetailResponse: + type: object + properties: + success: + type: boolean + message: + type: string + data: + type: object + properties: + id: + type: string + title: + type: string + desc: + type: string + member: + type: array + items: + $ref: '#/components/schemas/AnnouncementMember' + + + # Calendar + CalendarBase: + type: object + properties: + id: + type: string + dateStart: + type: string + format: date-time + timeStart: + type: string + timeEnd: + type: string + createdAt: + type: string + format: date-time + title: + type: string + desc: + type: string + createdBy: + type: string + isActive: + type: boolean + + CalendarMember: + type: object + properties: + id: + type: string + idUser: + type: string + name: + type: string + email: + type: string + img: + type: string + nullable: true + + CalendarListResponse: + type: object + properties: + success: + type: boolean + message: + type: string + data: + type: array + items: + $ref: '#/components/schemas/CalendarBase' + + CalendarDetailResponse: + type: object + properties: + success: + type: boolean + message: + type: string + data: + type: object + properties: + id: + type: string + timeStart: + type: string + dateStart: + type: string + format: date-time + timeEnd: + type: string + createdAt: + type: string + format: date-time + title: + type: string + desc: + type: string + linkMeet: + type: string + repeatEventTyper: + type: string + repeatValue: + type: integer + member: + type: array + items: + $ref: '#/components/schemas/CalendarMember' + + + # Discussion + DiscussionBase: + type: object + properties: + id: + type: string + desc: + type: string + createdAt: + type: string + format: date-time + idDivision: + type: string + division: + type: string + totalKomentar: + type: integer + status: + type: string + + DiscussionComment: + type: object + properties: + id: + type: string + comment: + type: string + createdAt: + type: string + format: date-time + username: + type: string + userimg: + type: string + + DiscussionListResponse: + type: object + properties: + success: + type: boolean + message: + type: string + data: + type: array + items: + $ref: '#/components/schemas/DiscussionBase' + + DiscussionDetailResponse: + type: object + properties: + success: + type: boolean + message: + type: string + data: + type: object + properties: + id: + type: string + idDivision: + type: string + division: + type: string + isActive: + type: boolean + desc: + type: string + status: + type: string + createdAt: + type: string + format: date-time + createdBy: + type: string + komentar: + type: array + items: + $ref: '#/components/schemas/DiscussionComment' + + + # Group + Group: + type: object + properties: + id: + type: string + name: + type: string + user: + type: string + isActive: + type: boolean + required: + - id + - name + + # Position + Position: + type: object + properties: + id: + type: string + name: + type: string + idGroup: + type: string + user: + type: string + isActive: + type: boolean + required: + - id + - name + + # Discussion General + DiscussionGeneral: + type: object + properties: + id: + type: string + title: + type: string + desc: + type: string + user: + type: string + status: + type: integer + member: + type: array + items: + $ref: '#/components/schemas/User' + required: + - id + - title + + # Project + Project: + type: object + properties: + id: + type: string + name: + type: string + user: + type: string + status: + type: integer + member: + type: array + items: + $ref: '#/components/schemas/User' + required: + - id + - name + + # Division (mirip Project, disederhanakan) + Division: + type: object + properties: + id: + type: string + name: + type: string + desc: + type: string + user: + type: string + isActive: + type: boolean + member: + type: array + items: + $ref: '#/components/schemas/User' + required: + - id + - name + + # Task (mirip Project) + Task: + type: object + properties: + id: + type: string + title: + type: string + user: + type: string + status: + type: integer + idDivision: + type: string + required: + - id + - title + + # Document + + # Notification + NotificationReadBody: + type: object + properties: + user: + type: string + id: + type: string + required: + - user + - id + + # Generic Response + ApiResponse: + type: object + properties: + data: + type: object + success: + type: boolean + required: + - data + + + + + + +paths: + # Announcement + /announcement: + get: + tags: + - Announcement + summary: Get announcements + parameters: + - name: desa + in: query + required: true + schema: + type: string + - name: searh + in: query + schema: + type: string + - name: page + in: query + schema: + type: integer + - name: get + in: query + schema: + type: integer + - name: active + in: query + schema: + type: boolean + responses: + '200': + description: List of announcements + content: + application/json: + schema: + $ref: '#/components/schemas/AnnouncementListResponse' + + /announcement/{id}: + get: + tags: + - Announcement + summary: Get one announcement + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: Announcement details + content: + application/json: + schema: + $ref: '#/components/schemas/AnnouncementDetailResponse' + + + # Banner + /banner: + get: + tags: + - Banner + summary: Get banners + parameters: + - name: desa + in: query + required: true + schema: + type: string + - name: page + in: query + schema: + type: integer + - name: get + in: query + schema: + type: integer + - name: search + in: query + schema: + type: string + - name: active + in: query + schema: + type: boolean + responses: + '200': + description: List of banners + content: + application/json: + schema: + $ref: '#/components/schemas/BannerListResponse' + + /banner/{id}: + get: + tags: + - Banner + summary: Get one banner + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: Banner details + content: + application/json: + schema: + $ref: '#/components/schemas/BannerDetailResponse' + + + # Calendar + /calendar: + get: + tags: + - Calendar + summary: Get calendar by date and division + parameters: + - name: desa + in: query + required: true + schema: + type: string + - name: date + in: query + schema: + type: string + - name: division + in: query + schema: + type: string + - name: active + in: query + schema: + type: boolean + - name: search + in: query + schema: + type: string + - name: page + in: query + schema: + type: integer + - name: get + in: query + schema: + type: integer + responses: + '200': + description: Calendar events + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/CalendarListResponse' + /calendar/{id}: + get: + tags: + - Calendar + summary: Get one calendar event + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: Event details + content: + application/json: + schema: + $ref: '#/components/schemas/CalendarDetailResponse' + + + # Discussion + /discussion: + get: + tags: + - Discussion + summary: Get discussions division + parameters: + - name: desa + in: query + required: true + schema: + type: string + - name: division + in: query + schema: + type: string + - name: status + in: query + schema: + type: string + enum: [open, close] + - name: active + in: query + schema: + type: boolean + - name: search + in: query + schema: + type: string + - name: page + in: query + schema: + type: integer + - name: get + in: query + schema: + type: integer + responses: + '200': + description: List of discussions + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DiscussionListResponse' + + /discussion/{id}: + get: + tags: + - Discussion + summary: Get one discussion division + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: Discussion details + content: + application/json: + schema: + $ref: '#/components/schemas/DiscussionDetailResponse' + + + # Home Data + /home: + get: + tags: + - Home + summary: Get home data by category + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: cat + in: query + required: true + schema: + type: string + enum: [kegiatan, division, progress, dokumen, event, discussion, header, check-late-project] + responses: + '200': + description: Home data + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + + /home/search: + get: + tags: + - Home + summary: Search home + parameters: + - name: search + in: query + required: true + schema: + type: string + - name: user + in: query + required: true + schema: + type: string + responses: + '200': + description: Search results + content: + application/json: + schema: + type: array + items: + type: object + + /home/notification: + get: + tags: + - Notification + summary: Get notifications + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: page + in: query + schema: + type: integer + responses: + '200': + description: List of notifications + content: + application/json: + schema: + type: array + items: + type: object + + # Group + /group: + get: + tags: + - Group + summary: Get groups + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: active + in: query + required: true + schema: + type: string + - name: search + in: query + required: true + schema: + type: string + responses: + '200': + description: List of groups + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Group' + + # Position + /position: + get: + tags: + - Position + summary: Get positions + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: active + in: query + required: true + schema: + type: string + - name: group + in: query + schema: + type: string + - name: search + in: query + required: true + schema: + type: string + responses: + '200': + description: List of positions + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Position' + + # User + /user: + get: + tags: + - User + summary: Get users + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: active + in: query + required: true + schema: + type: string + - name: group + in: query + schema: + type: string + - name: search + in: query + required: true + schema: + type: string + - name: page + in: query + schema: + type: integer + responses: + '200': + description: List of users + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + + /user/{id}: + get: + tags: + - User + summary: Get profile + parameters: + - name: id + in: path + required: true + schema: + type: string + responses: + '200': + description: User profile + content: + application/json: + schema: + $ref: '#/components/schemas/User' + + # Discussion General + /discussion-general: + get: + tags: + - DiscussionGeneral + summary: Get discussion general + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: active + in: query + required: true + schema: + type: string + - name: group + in: query + schema: + type: string + - name: search + in: query + required: true + schema: + type: string + - name: page + in: query + schema: + type: integer + responses: + '200': + description: List of discussions + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DiscussionGeneral' + + /discussion-general/{id}: + get: + tags: + - DiscussionGeneral + summary: Get one discussion general + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: user + in: query + required: true + schema: + type: string + - name: cat + in: query + required: true + schema: + type: string + responses: + '200': + description: Discussion details + content: + application/json: + schema: + $ref: '#/components/schemas/DiscussionGeneral' + + # Project + /project: + get: + tags: + - Project + summary: Get projects + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: status + in: query + required: true + schema: + type: string + - name: group + in: query + schema: + type: string + - name: search + in: query + required: true + schema: + type: string + - name: cat + in: query + schema: + type: string + - name: page + in: query + schema: + type: integer + responses: + '200': + description: List of projects + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Project' + + /project/{id}: + get: + tags: + - Project + summary: Get one project + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: user + in: query + required: true + schema: + type: string + - name: cat + in: query + required: true + schema: + type: string + enum: [data, progress, task, file, member, link] + responses: + '200': + description: Project details + content: + application/json: + schema: + $ref: '#/components/schemas/Project' + + # Division + /division: + get: + tags: + - Division + summary: Get divisions + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: active + in: query + schema: + type: string + - name: group + in: query + schema: + type: string + - name: search + in: query + required: true + schema: + type: string + - name: cat + in: query + schema: + type: string + - name: page + in: query + schema: + type: integer + responses: + '200': + description: List of divisions + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Division' + /division/{id}: + get: + tags: + - Division + summary: Get one division detail + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: user + in: query + required: true + schema: + type: string + responses: + '200': + description: Division details + content: + application/json: + schema: + $ref: '#/components/schemas/Division' + + /division/{id}/detail: + get: + tags: + - Division + summary: Get division one feature + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: user + in: query + required: true + schema: + type: string + - name: cat + in: query + required: true + schema: + type: string + enum: [jumlah, today-task, new-file, new-discussion, check-member, check-admin] + responses: + '200': + description: Feature data + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + + /division/{id}/member: + get: + tags: + - Division + summary: Get division members + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: user + in: query + required: true + schema: + type: string + - name: search + in: query + required: true + schema: + type: string + responses: + '200': + description: List of members + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + + /division/report: + get: + tags: + - Division + summary: Get division report + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: cat + in: query + required: true + schema: + type: string + enum: [table-progress, lainnya] + - name: date + in: query + required: true + schema: + type: string + - name: date-end + in: query + required: true + schema: + type: string + - name: division + in: query + required: true + schema: + type: string + - name: group + in: query + schema: + type: string + responses: + '200': + description: Report data + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + + /division/more: + get: + tags: + - Division + summary: Get list division by id division + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: search + in: query + required: true + schema: + type: string + - name: division + in: query + required: true + schema: + type: string + responses: + '200': + description: List of divisions + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Division' + + # Task + /task: + get: + tags: + - Task + summary: Get tasks + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: status + in: query + required: true + schema: + type: string + - name: division + in: query + required: true + schema: + type: string + - name: search + in: query + required: true + schema: + type: string + - name: page + in: query + schema: + type: integer + responses: + '200': + description: List of tasks + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Task' + + /task/{id}: + get: + tags: + - Task + summary: Get one task + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: user + in: query + required: true + schema: + type: string + - name: cat + in: query + required: true + schema: + type: string + enum: [data, progress, task, file, member, link] + responses: + '200': + description: Task details + content: + application/json: + schema: + $ref: '#/components/schemas/Task' + + /task/detail/{id}: + get: + tags: + - Task + summary: Get task tugas + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: user + in: query + required: true + schema: + type: string + - name: cat + in: query + schema: + type: string + responses: + '200': + description: Tugas details + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + + # Document + /document: + get: + tags: + - Document + summary: Get documents + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: path + in: query + required: true + schema: + type: string + - name: division + in: query + required: true + schema: + type: string + - name: category + in: query + required: true + schema: + type: string + enum: [all, folder] + responses: + '200': + description: List of documents + content: + application/json: + schema: + type: array + items: + type: object + + /document/more: + get: + tags: + - Document + summary: Get document info + parameters: + - name: user + in: query + required: true + schema: + type: string + - name: item + in: query + required: true + schema: + type: string + - name: cat + in: query + required: true + schema: + type: string + enum: [share, lainnya] + responses: + '200': + description: Info + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + + +security: + - bearerAuth: [] + +tags: + - name: Announcement + description: Announcements + - name: Banner + description: Banner management + - name: Calendar + description: Calendar events + - name: Discussion + description: Division discussions + + - name: Home + description: Home and search + - name: Group + description: Group management + - name: Position + description: Position management + - name: User + description: User management + - name: DiscussionGeneral + description: General discussions + - name: Project + description: Project management + - name: Division + description: Division management + - name: Task + description: Task management + - name: Document + description: Document management + - name: Notification + description: Notifications From 54d708d10948f2e5603f183b1be7e0429a9acd91 Mon Sep 17 00:00:00 2001 From: amal Date: Wed, 17 Sep 2025 10:58:48 +0800 Subject: [PATCH 08/21] upd: api ai diskusi umum Deskripsi: - api ai list diskusi umum - api ai detail diskusi umum NO Issues --- .../api/ai/discussion-general/[id]/route.ts | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/src/app/api/ai/discussion-general/[id]/route.ts b/src/app/api/ai/discussion-general/[id]/route.ts index 3df7047..aae67dd 100644 --- a/src/app/api/ai/discussion-general/[id]/route.ts +++ b/src/app/api/ai/discussion-general/[id]/route.ts @@ -1,7 +1,5 @@ import { prisma } from "@/module/_global"; import _ from "lodash"; -import moment from "moment"; -import "moment/locale/id"; import { NextResponse } from "next/server"; @@ -10,12 +8,15 @@ export async function GET(request: Request, context: { params: { id: string } }) try { let dataFix const { id } = context.params + const { searchParams } = new URL(request.url); const kategori = searchParams.get("cat"); + const idVillage = searchParams.get("desa"); const cek = await prisma.discussion.count({ where: { id, + idVillage: String(idVillage) } }) @@ -23,33 +24,7 @@ export async function GET(request: Request, context: { params: { id: string } }) return NextResponse.json({ success: false, message: "Gagal mendapatkan diskusi, data tidak ditemukan" }, { status: 404 }); } - if (kategori == "detail") { - const data = await prisma.discussion.findUnique({ - where: { - id, - }, - select: { - isActive: true, - id: true, - title: true, - idGroup: true, - desc: true, - status: true, - createdAt: true, - } - }) - - dataFix = { - id: data?.id, - isActive: data?.isActive, - idGroup: data?.idGroup, - title: data?.title, - desc: data?.desc, - status: data?.status, - createdAt: moment(data?.createdAt).format("ll"), - } - - } else if (kategori == "komentar") { + if (kategori == "comment") { const data = await prisma.discussionComment.findMany({ where: { idDiscussion: id, @@ -70,13 +45,12 @@ export async function GET(request: Request, context: { params: { id: string } }) }) dataFix = data.map((v: any) => ({ - ..._.omit(v, ["createdAt", "User",]), - createdAt: moment(v.createdAt).format("lll").replace('pukul', ''), + ..._.omit(v, ["User",]), username: v.User.name, img: v.User.img })) - } else if (kategori == "anggota") { + } else if (kategori == "member") { const data = await prisma.discussionMember.findMany({ where: { idDiscussion: id, @@ -98,6 +72,38 @@ export async function GET(request: Request, context: { params: { id: string } }) name: v.User.name, img: v.User.img })) + } else { + const data = await prisma.discussion.findUnique({ + where: { + id, + idVillage: String(idVillage) + }, + select: { + isActive: true, + id: true, + title: true, + idGroup: true, + desc: true, + status: true, + createdAt: true, + Group: { + select: { + name: true, + } + } + } + }) + + dataFix = { + 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 + } } From 006754a33776abcc99f22bb915c2731124cb13cf Mon Sep 17 00:00:00 2001 From: amal Date: Wed, 17 Sep 2025 14:29:53 +0800 Subject: [PATCH 09/21] upd: api ai Deskripsi: - list divisi - detail divisi No Issues --- src/app/api/ai/division/[id]/route.ts | 63 ++++++++++++++++++++ src/app/api/ai/division/route.ts | 84 +++++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 src/app/api/ai/division/[id]/route.ts create mode 100644 src/app/api/ai/division/route.ts diff --git a/src/app/api/ai/division/[id]/route.ts b/src/app/api/ai/division/[id]/route.ts new file mode 100644 index 0000000..5e5d475 --- /dev/null +++ b/src/app/api/ai/division/[id]/route.ts @@ -0,0 +1,63 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + + +// GET ONE DATA DIVISI :: UNTUK TAMPIL DATA DI HALAMAN EDIT DAN INFO +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + const { searchParams } = new URL(request.url); + const idVillage = searchParams.get("desa"); + + const data = await prisma.division.findUnique({ + where: { + id: String(id), + idVillage: String(idVillage) + } + }); + + if (!data) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, data tidak ditemukan", }, { status: 404 }); + } + + const member = 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 fixMember = member.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + img: v.User.img + })) + + const dataFix = { + ...data, + member: fixMember + } + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: dataFix, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/ai/division/route.ts b/src/app/api/ai/division/route.ts new file mode 100644 index 0000000..7c65213 --- /dev/null +++ b/src/app/api/ai/division/route.ts @@ -0,0 +1,84 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + + +// GET ALL DATA DIVISI == LIST DATA DIVISI +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const idVillage = searchParams.get("desa"); + const idGroup = searchParams.get("group"); + const name = searchParams.get('search'); + const page = searchParams.get('page'); + const active = searchParams.get("active"); + const get = searchParams.get('get') + + let getFix = 10; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: active == 'false' ? false : true, + idVillage: String(idVillage), + name: { + contains: (name == undefined || name == "null") ? "" : name, + mode: "insensitive" + } + } + + if (idGroup != "null" && idGroup != undefined && idGroup != "") { + kondisi = { + ...kondisi, + idGroup: String(idGroup) + } + } + + + 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 allData = data.map((v: any) => ({ + ..._.omit(v, ["DivisionMember", "Group"]), + group: v.Group.name, + jumlahMember: v.DivisionMember.length, + })) + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: allData }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file From d3f4478bb157ac823bf0f7602e5bfc5f69a6f4d0 Mon Sep 17 00:00:00 2001 From: amal Date: Wed, 17 Sep 2025 17:32:43 +0800 Subject: [PATCH 10/21] upd: api ai Deskripsi: - document divisi - swagger watcher nb: blm selesai NO Issues --- darmasaba-api-ai.yml | 648 ++++++++++++++++--------------- src/app/api/ai/document/route.ts | 147 +++++++ 2 files changed, 476 insertions(+), 319 deletions(-) create mode 100644 src/app/api/ai/document/route.ts diff --git a/darmasaba-api-ai.yml b/darmasaba-api-ai.yml index 9ae1e25..7c83eae 100644 --- a/darmasaba-api-ai.yml +++ b/darmasaba-api-ai.yml @@ -41,6 +41,14 @@ components: status: type: integer + BaseResponse: + type: object + properties: + success: + type: boolean + message: + type: string + @@ -329,6 +337,176 @@ components: $ref: '#/components/schemas/DiscussionComment' + # Discussion General + DiskusiUmumListResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + type: object + properties: + id: + type: string + title: + type: string + desc: + type: string + createdAt: + type: string + format: date-time + totalKomentar: + type: integer + status: + type: string + group: + type: string + + DiskusiUmumDetailResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: object + properties: + id: + type: string + isActive: + type: boolean + idGroup: + type: string + group: + type: string + title: + type: string + desc: + type: string + status: + type: string + createdAt: + type: string + format: date-time + + DiskusiUmumMemberResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + type: object + properties: + idUser: + type: string + name: + type: string + img: + type: string + nullable: true + + DiskusiUmumCommentResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + type: object + properties: + id: + type: string + comment: + type: string + createdAt: + type: string + format: date-time + idUser: + type: string + username: + type: string + img: + type: string + nullable: true + + + + # Division (mirip Project, disederhanakan) + DivisiListResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + type: object + properties: + id: + type: string + name: + type: string + desc: + type: string + idGroup: + type: string + group: + type: string + jumlahMember: + type: integer + + DivisiDetailResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: object + properties: + id: + type: string + idVillage: + type: string + idGroup: + type: string + name: + type: string + desc: + type: string + isActive: + type: boolean + createdBy: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + member: + type: array + items: + type: object + properties: + id: + type: string + isAdmin: + type: boolean + idUser: + type: string + name: + type: string + img: + type: string + nullable: true + + + + # Group Group: type: object @@ -363,28 +541,6 @@ components: - id - name - # Discussion General - DiscussionGeneral: - type: object - properties: - id: - type: string - title: - type: string - desc: - type: string - user: - type: string - status: - type: integer - member: - type: array - items: - $ref: '#/components/schemas/User' - required: - - id - - title - # Project Project: type: object @@ -405,28 +561,6 @@ components: - id - name - # Division (mirip Project, disederhanakan) - Division: - type: object - properties: - id: - type: string - name: - type: string - desc: - type: string - user: - type: string - isActive: - type: boolean - member: - type: array - items: - $ref: '#/components/schemas/User' - required: - - id - - name - # Task (mirip Project) Task: type: object @@ -720,6 +854,153 @@ paths: $ref: '#/components/schemas/DiscussionDetailResponse' + # Discussion General + /discussion-general: + get: + tags: + - DiscussionGeneral + summary: Get discussion general + parameters: + - name: desa + in: query + required: true + schema: + type: string + - name: group + in: query + schema: + type: string + - name: search + in: query + schema: + type: string + - name: status + in: query + schema: + type: string + enum: [open, close] + - name: active + in: query + schema: + type: boolean + - name: page + in: query + schema: + type: integer + - name: get + in: query + schema: + type: integer + responses: + '200': + description: List of discussions + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DiskusiUmumListResponse' + + /discussion-general/{id}: + get: + tags: + - DiscussionGeneral + summary: Get one discussion general + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: desa + in: query + required: true + schema: + type: string + - name: cat + in: query + schema: + type: string + enum: [detail, member, comment] + responses: + '200': + description: Discussion details + content: + application/json: + schema: + oneOf: + - $ref: '#/components/schemas/DiskusiUmumDetailResponse' + - $ref: '#/components/schemas/DiskusiUmumMemberResponse' + - $ref: '#/components/schemas/DiskusiUmumCommentResponse' + + + # Division + /division: + get: + tags: + - Division + summary: Get divisions + parameters: + - name: desa + in: query + required: true + schema: + type: string + - name: active + in: query + schema: + type: boolean + - name: group + in: query + schema: + type: string + - name: search + in: query + schema: + type: string + - name: page + in: query + schema: + type: integer + - name: get + in: query + schema: + type: integer + responses: + '200': + description: List of divisions + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/DivisiListResponse' + + /division/{id}: + get: + tags: + - Division + summary: Get one division detail + parameters: + - name: id + in: path + required: true + schema: + type: string + - name: desa + in: query + required: true + schema: + type: string + responses: + '200': + description: Division details + content: + application/json: + schema: + $ref: '#/components/schemas/DivisiDetailResponse' + + # Home Data /home: get: @@ -924,75 +1205,6 @@ paths: schema: $ref: '#/components/schemas/User' - # Discussion General - /discussion-general: - get: - tags: - - DiscussionGeneral - summary: Get discussion general - parameters: - - name: user - in: query - required: true - schema: - type: string - - name: active - in: query - required: true - schema: - type: string - - name: group - in: query - schema: - type: string - - name: search - in: query - required: true - schema: - type: string - - name: page - in: query - schema: - type: integer - responses: - '200': - description: List of discussions - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/DiscussionGeneral' - - /discussion-general/{id}: - get: - tags: - - DiscussionGeneral - summary: Get one discussion general - parameters: - - name: id - in: path - required: true - schema: - type: string - - name: user - in: query - required: true - schema: - type: string - - name: cat - in: query - required: true - schema: - type: string - responses: - '200': - description: Discussion details - content: - application/json: - schema: - $ref: '#/components/schemas/DiscussionGeneral' - # Project /project: get: @@ -1067,208 +1279,6 @@ paths: schema: $ref: '#/components/schemas/Project' - # Division - /division: - get: - tags: - - Division - summary: Get divisions - parameters: - - name: user - in: query - required: true - schema: - type: string - - name: active - in: query - schema: - type: string - - name: group - in: query - schema: - type: string - - name: search - in: query - required: true - schema: - type: string - - name: cat - in: query - schema: - type: string - - name: page - in: query - schema: - type: integer - responses: - '200': - description: List of divisions - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Division' - /division/{id}: - get: - tags: - - Division - summary: Get one division detail - parameters: - - name: id - in: path - required: true - schema: - type: string - - name: user - in: query - required: true - schema: - type: string - responses: - '200': - description: Division details - content: - application/json: - schema: - $ref: '#/components/schemas/Division' - - /division/{id}/detail: - get: - tags: - - Division - summary: Get division one feature - parameters: - - name: id - in: path - required: true - schema: - type: string - - name: user - in: query - required: true - schema: - type: string - - name: cat - in: query - required: true - schema: - type: string - enum: [jumlah, today-task, new-file, new-discussion, check-member, check-admin] - responses: - '200': - description: Feature data - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - - /division/{id}/member: - get: - tags: - - Division - summary: Get division members - parameters: - - name: id - in: path - required: true - schema: - type: string - - name: user - in: query - required: true - schema: - type: string - - name: search - in: query - required: true - schema: - type: string - responses: - '200': - description: List of members - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/User' - - /division/report: - get: - tags: - - Division - summary: Get division report - parameters: - - name: user - in: query - required: true - schema: - type: string - - name: cat - in: query - required: true - schema: - type: string - enum: [table-progress, lainnya] - - name: date - in: query - required: true - schema: - type: string - - name: date-end - in: query - required: true - schema: - type: string - - name: division - in: query - required: true - schema: - type: string - - name: group - in: query - schema: - type: string - responses: - '200': - description: Report data - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - - /division/more: - get: - tags: - - Division - summary: Get list division by id division - parameters: - - name: user - in: query - required: true - schema: - type: string - - name: search - in: query - required: true - schema: - type: string - - name: division - in: query - required: true - schema: - type: string - responses: - '200': - description: List of divisions - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Division' - # Task /task: get: @@ -1449,6 +1459,10 @@ tags: description: Calendar events - name: Discussion description: Division discussions + - name: DiscussionGeneral + description: General discussions + - name: Division + description: Division management - name: Home description: Home and search @@ -1458,12 +1472,8 @@ tags: description: Position management - name: User description: User management - - name: DiscussionGeneral - description: General discussions - name: Project description: Project management - - name: Division - description: Division management - name: Task description: Task management - name: Document diff --git a/src/app/api/ai/document/route.ts b/src/app/api/ai/document/route.ts new file mode 100644 index 0000000..57b619b --- /dev/null +++ b/src/app/api/ai/document/route.ts @@ -0,0 +1,147 @@ +import { prisma } from "@/module/_global"; +import { funGetUserByCookies } from "@/module/auth"; +import { createLogUser } from "@/module/user"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; + + +// GET ALL DOCUMENT +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const idDivision = searchParams.get("division"); + const villageId = searchParams.get("desa"); + const path = searchParams.get("path"); + const category = searchParams.get("category"); + const active = searchParams.get("active"); + const search = searchParams.get("search"); + const page = searchParams.get("page"); + const get = searchParams.get("get"); + + + let kondisi: any = { + isActive: true, + idDivision: String(idDivision), + path: (path == "undefined" || path == "null" || path == "" || path == null) ? "home" : path + } + + let formatDataShare: any[] = []; + + if (category == "folder") { + kondisi = { + isActive: true, + idDivision: String(idDivision), + path: (path == "undefined" || path == "null" || path == "" || path == null) ? "home" : path, + category: "FOLDER" + } + } else { + if (path == "home" || path == "null" || path == "undefined") { + const dataShare = await prisma.divisionDocumentShare.findMany({ + where: { + isActive: true, + idDivision: String(idDivision), + 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) => ({ + ..._.omit(v, ["DivisionDocumentFolderFile"]), + 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 + })) + + } else { + kondisi = { + isActive: true, + path: (path == "undefined" || path == "null" || path == null) ? "home" : path + } + } + } + + + const data = await prisma.divisionDocumentFolderFile.findMany({ + 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", "createdAt", "updatedAt"]), + createdBy: v.User.name, + createdAt: v.createdAt, + updatedAt: v.updatedAt, + share: false + })) + + if (formatDataShare.length > 0) { + allData.push(...formatDataShare) + } + + const formatData = _.orderBy(allData, ['category', 'createdAt'], ['desc', 'desc']); + + const fixData = formatData.map((v: any) => ({ + ..._.omit(v, ["createdAt", "updatedAt"]), + createdAt: moment(v.createdAt).format("DD-MM-YYYY HH:mm"), + updatedAt: moment(v.updatedAt).format("DD-MM-YYYY HH:mm"), + })) + + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan item", data: fixData }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan item, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file From 18589b7de71f6aa371eccbd755502389009529b7 Mon Sep 17 00:00:00 2001 From: amal Date: Fri, 19 Sep 2025 10:56:02 +0800 Subject: [PATCH 11/21] upd: api ai document divisi Deskripsi: - list dokumen dan file divisi NO Issues --- src/app/api/ai/document/route.ts | 146 +++++++++++++++---------------- 1 file changed, 72 insertions(+), 74 deletions(-) diff --git a/src/app/api/ai/document/route.ts b/src/app/api/ai/document/route.ts index 57b619b..2c52ec7 100644 --- a/src/app/api/ai/document/route.ts +++ b/src/app/api/ai/document/route.ts @@ -1,6 +1,4 @@ import { prisma } from "@/module/_global"; -import { funGetUserByCookies } from "@/module/auth"; -import { createLogUser } from "@/module/user"; import _ from "lodash"; import moment from "moment"; import { NextResponse } from "next/server"; @@ -13,88 +11,95 @@ export async function GET(request: Request) { const idDivision = searchParams.get("division"); const villageId = searchParams.get("desa"); const path = searchParams.get("path"); - const category = searchParams.get("category"); const active = searchParams.get("active"); const search = searchParams.get("search"); const page = searchParams.get("page"); const get = searchParams.get("get"); + let getFix = 10; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; let kondisi: any = { - isActive: true, - idDivision: String(idDivision), - path: (path == "undefined" || path == "null" || path == "" || path == null) ? "home" : path + Division: { + idVillage: String(villageId) + }, + isActive: active == 'false' ? false : true, + path: (path == "undefined" || path == "null" || path == "" || path == null || path == undefined) ? "home" : path, + name: { + contains: (search == undefined || search == "null") ? "" : search, + mode: "insensitive" + } + } + + if (idDivision != "null" && idDivision != undefined && idDivision != "") { + kondisi = { + ...kondisi, + idDivision: String(idDivision) + } } let formatDataShare: any[] = []; - if (category == "folder") { - kondisi = { - isActive: true, - idDivision: String(idDivision), - path: (path == "undefined" || path == "null" || path == "" || path == null) ? "home" : path, - category: "FOLDER" - } - } else { - if (path == "home" || path == "null" || path == "undefined") { - const dataShare = await prisma.divisionDocumentShare.findMany({ - where: { - isActive: true, - idDivision: String(idDivision), - 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' + if (path == "home" || path == "null" || path == "undefined" || path == null || path == undefined || path == "") { + const dataShare = await prisma.divisionDocumentShare.findMany({ + where: { + isActive: true, + idDivision: String(idDivision), + 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 } } - }) - - formatDataShare = dataShare.map((v: any) => ({ - ..._.omit(v, ["DivisionDocumentFolderFile"]), - 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 - })) - - } else { - kondisi = { - isActive: true, - path: (path == "undefined" || path == "null" || path == null) ? "home" : path + }, + orderBy: { + DivisionDocumentFolderFile: { + createdAt: 'desc' + } } - } + }) + + formatDataShare = dataShare.map((v: any) => ({ + ..._.omit(v, ["DivisionDocumentFolderFile"]), + 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, @@ -130,15 +135,8 @@ export async function GET(request: Request) { const formatData = _.orderBy(allData, ['category', 'createdAt'], ['desc', 'desc']); - const fixData = formatData.map((v: any) => ({ - ..._.omit(v, ["createdAt", "updatedAt"]), - createdAt: moment(v.createdAt).format("DD-MM-YYYY HH:mm"), - updatedAt: moment(v.updatedAt).format("DD-MM-YYYY HH:mm"), - })) - - - return NextResponse.json({ success: true, message: "Berhasil mendapatkan item", data: fixData }, { status: 200 }); + return NextResponse.json({ success: true, message: "Berhasil mendapatkan item", data: formatData }, { status: 200 }); } catch (error) { console.error(error); From 51b39396ec4fea40076e04ce13dd45bef29c2f26 Mon Sep 17 00:00:00 2001 From: amal Date: Fri, 19 Sep 2025 11:08:19 +0800 Subject: [PATCH 12/21] upd: api ai group Deskripsi: - list group NO Issues --- src/app/api/ai/group/route.ts | 45 +++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 src/app/api/ai/group/route.ts diff --git a/src/app/api/ai/group/route.ts b/src/app/api/ai/group/route.ts new file mode 100644 index 0000000..3b85c37 --- /dev/null +++ b/src/app/api/ai/group/route.ts @@ -0,0 +1,45 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const villageId = searchParams.get("desa"); + const isActive = searchParams.get("active"); + const search = searchParams.get('search'); + const page = searchParams.get('page') + const get = searchParams.get('get') + + let getFix = 10; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; + + const data = await prisma.group.findMany({ + skip: dataSkip, + take: getFix, + where: { + isActive: isActive == 'false' ? false : true, + idVillage: String(villageId), + name: { + contains: (search == undefined || search == null) ? "" : search, + mode: "insensitive" + } + }, + orderBy: { + name: 'asc' + } + }); + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan grup", data, }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan grup, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file From fbfdc21b9dfa1646ef2e239885746ba12613a810 Mon Sep 17 00:00:00 2001 From: amal Date: Fri, 19 Sep 2025 11:42:45 +0800 Subject: [PATCH 13/21] upd: api ai positision Deskripsi: - list position No Issues --- src/app/api/ai/position/route.ts | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/app/api/ai/position/route.ts diff --git a/src/app/api/ai/position/route.ts b/src/app/api/ai/position/route.ts new file mode 100644 index 0000000..3aaca1f --- /dev/null +++ b/src/app/api/ai/position/route.ts @@ -0,0 +1,79 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + + +// GET ALL POSITION +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const idVillage = searchParams.get("desa"); + const idGroup = searchParams.get("group"); + const active = searchParams.get('active'); + const search = searchParams.get('search') + const page = searchParams.get('page') + const get = searchParams.get('get') + + let getFix = 10; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: active == 'false' ? false : true, + Group: { + idVillage: String(idVillage) + }, + name: { + contains: (search == undefined || search == null) ? "" : search, + mode: "insensitive" + } + } + + if (idGroup != "null" && idGroup != undefined && idGroup != "") { + kondisi = { + ...kondisi, + idGroup: String(idGroup) + } + } + + + + 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 allData = positions.map((v: any) => ({ + ..._.omit(v, ["Group"]), + group: v.Group.name + })) + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan jabatan", data: allData }, { status: 200 }); + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan jabatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file From c51692079efcd6d668bf907966d8d4668b076df7 Mon Sep 17 00:00:00 2001 From: amal Date: Fri, 19 Sep 2025 12:33:31 +0800 Subject: [PATCH 14/21] upd: api ai project Deskripsi: - list project - detail project No Issues' --- src/app/api/ai/project/[id]/route.ts | 172 +++++++++++++++++++++++++++ src/app/api/ai/project/route.ts | 107 +++++++++++++++++ 2 files changed, 279 insertions(+) create mode 100644 src/app/api/ai/project/[id]/route.ts create mode 100644 src/app/api/ai/project/route.ts diff --git a/src/app/api/ai/project/[id]/route.ts b/src/app/api/ai/project/[id]/route.ts new file mode 100644 index 0000000..83853a9 --- /dev/null +++ b/src/app/api/ai/project/[id]/route.ts @@ -0,0 +1,172 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import moment from "moment"; +import { NextResponse } from "next/server"; + + +// GET DETAIL PROJECT / GET ONE PROJECT +export async function GET(request: Request, context: { params: { id: string } }) { + try { + let allData + const { id } = context.params; + const { searchParams } = new URL(request.url); + const kategori = searchParams.get("cat"); + + 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) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", }, { status: 404 }); + } + + + if (kategori == "data") { + const dataProgress = await prisma.projectTask.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + updatedAt: 'desc' + } + }) + + const semua = dataProgress.length + const selesai = _.filter(dataProgress, { status: 1 }).length + const progress = Math.ceil((selesai / semua) * 100) + + allData = { + 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, + } + + } else if (kategori == "task") { + const dataProgress = 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' + } + }) + + const formatData = dataProgress.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"), + })) + const dataFix = _.orderBy(formatData, 'createdAt', 'asc') + allData = dataFix + + } else if (kategori == "file") { + const dataFile = await prisma.projectFile.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + createdAt: 'asc' + }, + select: { + id: true, + name: true, + extension: true, + idStorage: true + } + }) + + allData = dataFile + + } else if (kategori == "member") { + const dataMember = 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 + } + } + } + }, + } + }) + + const fix = dataMember.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + email: v.User.email, + img: v.User.img, + position: v.User.Position.name + })) + + allData = fix + } else if (kategori == "link") { + const dataLink = await prisma.projectLink.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + createdAt: 'asc' + } + }) + allData = dataLink + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan kegiatan", data: allData }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan kegiatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/ai/project/route.ts b/src/app/api/ai/project/route.ts new file mode 100644 index 0000000..7db00f3 --- /dev/null +++ b/src/app/api/ai/project/route.ts @@ -0,0 +1,107 @@ +import { prisma } from "@/module/_global"; +import _, { ceil } from "lodash"; +import { NextResponse } from "next/server"; + + +// GET ALL DATA PROJECT +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const idVillage = searchParams.get('desa'); + const name = searchParams.get('search'); + const status = searchParams.get('status'); + const idGroup = searchParams.get("group"); + const page = searchParams.get('page'); + const get = searchParams.get('get'); + + let getFix = 10; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; + + + + + let kondisi: any = { + isActive: true, + idVillage: String(idVillage), + title: { + contains: (name == undefined || name == "null") ? "" : name, + mode: "insensitive" + }, + } + + if (status != "null" && status != undefined && status != "") { + kondisi = { + ...kondisi, + status: status == "segera" ? 0 : status == "dikerjakan" ? 1 : status == "selesai" ? 2 : status == "batal" ? 3 : 0 + } + } + + + if (idGroup != "null" && idGroup != undefined && idGroup != "") { + kondisi = { + ...kondisi, + idGroup: String(idGroup) + } + } + + + 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 omitData = data.map((v: any) => ({ + ..._.omit(v, ["ProjectMember", "ProjectTask", "status", "Group"]), + group: v.Group.name, + status: v.status == 0 ? "segera" : v.status == 1 ? "dikerjakan" : v.status == 2 ? "selesai" : v.status == 3 ? "batal" : "", + progress: ceil((v.ProjectTask.filter((i: any) => i.status == 1).length * 100) / v.ProjectTask.length), + member: v.ProjectMember.length + })) + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan kegiatan", data: omitData }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan kegiatan, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file From bbe016c03dba79238d00ac6a95203b814ce87da2 Mon Sep 17 00:00:00 2001 From: amal Date: Fri, 19 Sep 2025 16:47:52 +0800 Subject: [PATCH 15/21] upd: api ai Deskripsi: - list task - detail task No Issues --- src/app/api/ai/project/route.ts | 2 +- src/app/api/ai/task/[id]/route.ts | 180 ++++++++++++++++++++++++++++++ src/app/api/ai/task/route.ts | 104 +++++++++++++++++ 3 files changed, 285 insertions(+), 1 deletion(-) create mode 100644 src/app/api/ai/task/[id]/route.ts create mode 100644 src/app/api/ai/task/route.ts diff --git a/src/app/api/ai/project/route.ts b/src/app/api/ai/project/route.ts index 7db00f3..7715ee5 100644 --- a/src/app/api/ai/project/route.ts +++ b/src/app/api/ai/project/route.ts @@ -92,7 +92,7 @@ export async function GET(request: Request) { const omitData = data.map((v: any) => ({ ..._.omit(v, ["ProjectMember", "ProjectTask", "status", "Group"]), group: v.Group.name, - status: v.status == 0 ? "segera" : v.status == 1 ? "dikerjakan" : v.status == 2 ? "selesai" : v.status == 3 ? "batal" : "", + 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 })) diff --git a/src/app/api/ai/task/[id]/route.ts b/src/app/api/ai/task/[id]/route.ts new file mode 100644 index 0000000..75b3261 --- /dev/null +++ b/src/app/api/ai/task/[id]/route.ts @@ -0,0 +1,180 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import moment from "moment"; +import "moment/locale/id"; +import { NextResponse } from "next/server"; + + +// GET DETAIL TASK DIVISI / GET ONE +export async function GET(request: Request, context: { params: { id: string } }) { + try { + let allData + const { id } = context.params; + const { searchParams } = new URL(request.url); + const kategori = searchParams.get("cat"); + + 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) { + return NextResponse.json({ success: false, message: "Gagal mendapatkan kegiatan, data tidak ditemukan", }, { status: 404 }); + } + + if (kategori == "data") { + const dataProgress = await prisma.divisionProjectTask.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + updatedAt: 'desc' + } + }) + + const semua = dataProgress.length + const selesai = _.filter(dataProgress, { status: 1 }).length + const progress = Math.ceil((selesai / semua) * 100) + + + allData = { + 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: progress, + } + } else if (kategori == "task") { + const dataProgress = await prisma.divisionProjectTask.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + select: { + id: true, + title: true, + status: true, + dateStart: true, + dateEnd: true, + }, + orderBy: { + createdAt: 'asc' + } + }) + + const fix = dataProgress.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"), + })) + + allData = fix + + } else if (kategori == "file") { + const dataFile = await prisma.divisionProjectFile.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + select: { + id: true, + ContainerFileDivision: { + select: { + id: true, + name: true, + extension: true, + idStorage: true + } + } + } + }) + + const fix = dataFile.map((v: any) => ({ + ..._.omit(v, ["ContainerFileDivision"]), + nameInStorage: v.ContainerFileDivision.id, + name: v.ContainerFileDivision.name, + extension: v.ContainerFileDivision.extension, + idStorage: v.ContainerFileDivision.idStorage, + })) + + allData = fix + + } else if (kategori == "member") { + const dataMember = 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 + } + } + } + } + } + }) + + + const fix = dataMember.map((v: any) => ({ + ..._.omit(v, ["User"]), + name: v.User.name, + email: v.User.email, + img: v.User.img, + position: v.User.Position.name + })) + + allData = fix + } else if (kategori == "link") { + const dataLink = await prisma.divisionProjectLink.findMany({ + where: { + isActive: true, + idProject: String(id) + }, + orderBy: { + createdAt: 'asc' + } + }) + + allData = dataLink + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan tugas divisi", data: allData }, { status: 200 }); + + } + catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan tugas divisi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/ai/task/route.ts b/src/app/api/ai/task/route.ts new file mode 100644 index 0000000..3fd53dc --- /dev/null +++ b/src/app/api/ai/task/route.ts @@ -0,0 +1,104 @@ +import { prisma } from "@/module/_global"; +import _, { ceil } from "lodash"; +import { NextResponse } from "next/server"; + + +// GET ALL DATA TUGAS DIVISI +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const villageId = searchParams.get('desa'); + const division = searchParams.get('division'); + const search = searchParams.get('search'); + const status = searchParams.get('status'); + const page = searchParams.get('page'); + const get = searchParams.get('get'); + + let getFix = 10; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: true, + Division: { + idVillage: String(villageId) + }, + title: { + contains: (search == undefined || search == "null") ? "" : search, + mode: "insensitive" + } + } + + if (status != "null" && status != undefined && status != "" && status != null) { + kondisi = { + ...kondisi, + status: status == "segera" ? 0 : status == "dikerjakan" ? 1 : status == "selesai" ? 2 : status == "batal" ? 3 : 0 + } + } + + if (division != "null" && division != undefined && division != "" && division != null) { + kondisi = { + ...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 formatData = 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 NextResponse.json({ success: true, message: "Berhasil mendapatkan divisi", data: formatData }, { status: 200 }); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan divisi, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file From 09d1088face7c25abcdf61c898c6071a0d123ce1 Mon Sep 17 00:00:00 2001 From: amal Date: Fri, 19 Sep 2025 16:48:22 +0800 Subject: [PATCH 16/21] upd: swagger watcher --- darmasaba-api-ai.yml | 1444 +++++++++++++++++++++++++----------------- 1 file changed, 849 insertions(+), 595 deletions(-) diff --git a/darmasaba-api-ai.yml b/darmasaba-api-ai.yml index 7c83eae..43ca383 100644 --- a/darmasaba-api-ai.yml +++ b/darmasaba-api-ai.yml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: API AI Desa+ - description: API untuk AI Desa+ + description: API untuk sistem manajemen AI Desa+ version: 1.0.0 contact: name: API Support @@ -19,42 +19,35 @@ components: bearerFormat: JWT schemas: - # Common Schemas - User: - type: object - properties: - id: - type: string - name: - type: string - phone: - type: string - # Tambahkan properties lain sesuai kebutuhan - required: - - id - - Error: - type: object - properties: - message: - type: string - status: - type: integer - BaseResponse: type: object + required: + - success + - message properties: success: type: boolean message: type: string - - - + meta: + type: object + properties: + total: + type: integer + page: + type: integer + perPage: + type: integer # Banner BannerBase: type: object + required: + - id + - idVillage + - title + - image + - isActive properties: id: type: string @@ -76,31 +69,32 @@ components: format: date-time BannerListResponse: - type: object - properties: - success: - type: boolean - message: - type: string - data: - type: array - items: - $ref: '#/components/schemas/BannerBase' + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/BannerBase' BannerDetailResponse: - type: object - properties: - success: - type: boolean - message: - type: string - data: - $ref: '#/components/schemas/BannerBase' - + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + $ref: '#/components/schemas/BannerBase' # Announcement AnnouncementBase: type: object + required: + - id + - idVillage + - title + - desc + - isActive properties: id: type: string @@ -123,6 +117,9 @@ components: AnnouncementMember: type: object + required: + - idGroup + - idDivision properties: idGroup: type: string @@ -134,42 +131,42 @@ components: type: string AnnouncementListResponse: - type: object - properties: - success: - type: boolean - message: - type: string - data: - type: array - items: - $ref: '#/components/schemas/AnnouncementBase' - - AnnouncementDetailResponse: - type: object - properties: - success: - type: boolean - message: - type: string - data: - type: object + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object properties: - id: - type: string - title: - type: string - desc: - type: string - member: + data: type: array items: - $ref: '#/components/schemas/AnnouncementMember' + $ref: '#/components/schemas/AnnouncementBase' + AnnouncementDetailResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: object + properties: + id: + type: string + title: + type: string + desc: + type: string + member: + type: array + items: + $ref: '#/components/schemas/AnnouncementMember' # Calendar CalendarBase: type: object + required: + - id + - dateStart + - title + - isActive properties: id: type: string @@ -194,6 +191,11 @@ components: CalendarMember: type: object + required: + - id + - idUser + - name + - email properties: id: type: string @@ -208,58 +210,58 @@ components: nullable: true CalendarListResponse: - type: object - properties: - success: - type: boolean - message: - type: string - data: - type: array - items: - $ref: '#/components/schemas/CalendarBase' - - CalendarDetailResponse: - type: object - properties: - success: - type: boolean - message: - type: string - data: - type: object + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object properties: - id: - type: string - timeStart: - type: string - dateStart: - type: string - format: date-time - timeEnd: - type: string - createdAt: - type: string - format: date-time - title: - type: string - desc: - type: string - linkMeet: - type: string - repeatEventTyper: - type: string - repeatValue: - type: integer - member: + data: type: array items: - $ref: '#/components/schemas/CalendarMember' + $ref: '#/components/schemas/CalendarBase' + CalendarDetailResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: object + properties: + id: + type: string + timeStart: + type: string + dateStart: + type: string + format: date-time + timeEnd: + type: string + createdAt: + type: string + format: date-time + title: + type: string + desc: + type: string + linkMeet: + type: string + repeatEventType: + type: string + repeatValue: + type: integer + member: + type: array + items: + $ref: '#/components/schemas/CalendarMember' # Discussion DiscussionBase: type: object + required: + - id + - desc + - idDivision + - status properties: id: type: string @@ -276,9 +278,14 @@ components: type: integer status: type: string + enum: [open, close] DiscussionComment: type: object + required: + - id + - comment + - username properties: id: type: string @@ -291,53 +298,50 @@ components: type: string userimg: type: string + nullable: true DiscussionListResponse: - type: object - properties: - success: - type: boolean - message: - type: string - data: - type: array - items: - $ref: '#/components/schemas/DiscussionBase' - - DiscussionDetailResponse: - type: object - properties: - success: - type: boolean - message: - type: string - data: - type: object + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object properties: - id: - type: string - idDivision: - type: string - division: - type: string - isActive: - type: boolean - desc: - type: string - status: - type: string - createdAt: - type: string - format: date-time - createdBy: - type: string - komentar: + data: type: array items: - $ref: '#/components/schemas/DiscussionComment' + $ref: '#/components/schemas/DiscussionBase' + DiscussionDetailResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: object + properties: + id: + type: string + idDivision: + type: string + division: + type: string + isActive: + type: boolean + desc: + type: string + status: + type: string + enum: [open, close] + createdAt: + type: string + format: date-time + createdBy: + type: string + komentar: + type: array + items: + $ref: '#/components/schemas/DiscussionComment' - # Discussion General + # Discussion General DiskusiUmumListResponse: allOf: - $ref: '#/components/schemas/BaseResponse' @@ -347,6 +351,11 @@ components: type: array items: type: object + required: + - id + - title + - desc + - status properties: id: type: string @@ -361,6 +370,7 @@ components: type: integer status: type: string + enum: [open, close] group: type: string @@ -371,6 +381,11 @@ components: properties: data: type: object + required: + - id + - title + - desc + - status properties: id: type: string @@ -386,6 +401,7 @@ components: type: string status: type: string + enum: [open, close] createdAt: type: string format: date-time @@ -399,6 +415,9 @@ components: type: array items: type: object + required: + - idUser + - name properties: idUser: type: string @@ -417,6 +436,11 @@ components: type: array items: type: object + required: + - id + - comment + - idUser + - username properties: id: type: string @@ -433,9 +457,7 @@ components: type: string nullable: true - - - # Division (mirip Project, disederhanakan) + # Division DivisiListResponse: allOf: - $ref: '#/components/schemas/BaseResponse' @@ -445,6 +467,9 @@ components: type: array items: type: object + required: + - id + - name properties: id: type: string @@ -466,6 +491,10 @@ components: properties: data: type: object + required: + - id + - name + - isActive properties: id: type: string @@ -491,6 +520,10 @@ components: type: array items: type: object + required: + - id + - idUser + - name properties: id: type: string @@ -504,28 +537,92 @@ components: type: string nullable: true - - - - # Group - Group: + # Document + DocumentItem: type: object + required: + - id + - category + - name + - path properties: id: type: string + category: + type: string + enum: [FILE, FOLDER] name: type: string - user: + extension: + type: string + idStorage: + type: string + nullable: true + path: + type: string + createdBy: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + share: + type: boolean + + DocumentListResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/DocumentItem' + + # Group + GroupItem: + type: object + required: + - id + - idVillage + - name + - isActive + properties: + id: + type: string + idVillage: + type: string + name: type: string isActive: type: boolean + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + + GroupListResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/GroupItem' + + # Position + PositionItem: + type: object required: - id - name - - # Position - Position: - type: object + - idGroup + - isActive properties: id: type: string @@ -533,109 +630,336 @@ components: type: string idGroup: type: string - user: + group: type: string isActive: type: boolean - required: - - id - - name + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + + PositionListResponse: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/PositionItem' # Project - Project: - type: object - properties: - id: - type: string - name: - type: string - user: - type: string - status: - type: integer - member: - type: array - items: - $ref: '#/components/schemas/User' - required: - - id - - name + BaseResponseProjectList: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + type: object + required: + - id + - idGroup + - title + - status + properties: + id: + type: string + idGroup: + type: string + title: + type: string + desc: + type: string + nullable: true + group: + type: string + status: + type: string + enum: [segera, dikerjakan, selesai, batal] + progress: + type: integer + member: + type: integer - # Task (mirip Project) - Task: - type: object - properties: - id: - type: string - title: - type: string - user: - type: string - status: - type: integer - idDivision: - type: string - required: - - id - - title + BaseResponseProjectDetail: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: object + required: + - id + - idVillage + - idGroup + - title + - status + - isActive + properties: + id: + type: string + idVillage: + type: string + idGroup: + type: string + group: + type: string + title: + type: string + status: + type: string + enum: [segera, dikerjakan, selesai, batal] + desc: + type: string + nullable: true + reason: + type: string + nullable: true + report: + type: string + nullable: true + isActive: + type: boolean + progress: + type: integer - # Document - - # Notification - NotificationReadBody: - type: object - properties: - user: - type: string - id: - type: string - required: - - user - - id - - # Generic Response - ApiResponse: - type: object - properties: - data: - type: object - success: - type: boolean - required: - - data + BaseResponseTaskList: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + type: object + required: + - id + - idDivision + - title + - status + properties: + id: + type: string + idDivision: + type: string + title: + type: string + desc: + type: string + nullable: true + division: + type: string + status: + type: string + enum: [segera, dikerjakan, selesai, batal] + progress: + type: integer + member: + type: integer + BaseResponseTaskDetail: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: object + required: + - id + - idDivision + - title + - status + - isActive + properties: + id: + type: string + idDivision: + type: string + division: + type: string + title: + type: string + status: + type: string + desc: + type: string + nullable: true + reason: + type: string + nullable: true + report: + type: string + nullable: true + isActive: + type: boolean + progress: + type: integer + BaseResponseSubTaskList: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + type: object + required: + - id + - title + - status + properties: + id: + type: string + title: + type: string + status: + type: string + enum: [belum selesai, selesai] + dateStart: + type: string + format: date-time + dateEnd: + type: string + format: date-time + BaseResponseMemberList: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + type: object + required: + - id + - idUser + - name + - email + properties: + id: + type: string + idUser: + type: string + name: + type: string + email: + type: string + img: + type: string + nullable: true + position: + type: string + BaseResponseFileList: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + type: object + required: + - id + - name + - path + properties: + id: + type: string + name: + type: string + extension: + type: string + idStorage: + type: string + nullable: true + path: + type: string + createdBy: + type: string + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time + share: + type: boolean + BaseResponseLinkList: + allOf: + - $ref: '#/components/schemas/BaseResponse' + - type: object + properties: + data: + type: array + items: + type: object + required: + - id + - idProject + - link + - isActive + properties: + id: + type: string + idProject: + type: string + link: + type: string + isActive: + type: boolean + createdAt: + type: string + format: date-time + updatedAt: + type: string + format: date-time paths: # Announcement - /announcement: + /announcement: get: tags: - Announcement - summary: Get announcements + summary: Get list of announcements + description: Retrieves a paginated list of announcements filtered by village parameters: - name: desa in: query required: true + description: Village ID schema: type: string - - name: searh + - name: search in: query + description: Search term for announcement title or description schema: type: string - name: page in: query + description: Page number for pagination schema: type: integer + minimum: 1 + default: 1 - name: get in: query + description: Number of items per page schema: type: integer + minimum: 1 + default: 10 - name: active in: query + description: Filter by active status schema: type: boolean responses: @@ -646,15 +970,17 @@ paths: schema: $ref: '#/components/schemas/AnnouncementListResponse' - /announcement/{id}: + /announcement/{id}: get: tags: - Announcement - summary: Get one announcement + summary: Get announcement details + description: Retrieves details of a specific announcement parameters: - name: id in: path required: true + description: Announcement ID schema: type: string responses: @@ -665,33 +991,42 @@ paths: schema: $ref: '#/components/schemas/AnnouncementDetailResponse' - # Banner - /banner: + /banner: get: tags: - Banner - summary: Get banners + summary: Get list of banners + description: Retrieves a paginated list of banners filtered by village parameters: - name: desa in: query required: true + description: Village ID + schema: + type: string + - name: search + in: query + description: Search term for banner title schema: type: string - name: page in: query + description: Page number for pagination schema: type: integer + minimum: 1 + default: 1 - name: get in: query + description: Number of items per page schema: type: integer - - name: search - in: query - schema: - type: string + minimum: 1 + default: 10 - name: active in: query + description: Filter by active status schema: type: boolean responses: @@ -701,16 +1036,18 @@ paths: application/json: schema: $ref: '#/components/schemas/BannerListResponse' - - /banner/{id}: + + /banner/{id}: get: tags: - Banner - summary: Get one banner + summary: Get banner details + description: Retrieves details of a specific banner parameters: - name: id in: path required: true + description: Banner ID schema: type: string responses: @@ -721,61 +1058,74 @@ paths: schema: $ref: '#/components/schemas/BannerDetailResponse' - # Calendar - /calendar: + /calendar: get: tags: - Calendar - summary: Get calendar by date and division + summary: Get calendar events + description: Retrieves a paginated list of calendar events filtered by village, date, and division parameters: - name: desa in: query required: true + description: Village ID schema: type: string - name: date in: query + description: Filter by event date schema: type: string + format: date - name: division in: query + description: Filter by division ID schema: type: string - name: active in: query + description: Filter by active status schema: type: boolean - name: search in: query + description: Search term for event title or description schema: type: string - name: page in: query + description: Page number for pagination schema: type: integer + minimum: 1 + default: 1 - name: get in: query + description: Number of items per page schema: type: integer + minimum: 1 + default: 10 responses: '200': - description: Calendar events + description: List of calendar events content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/CalendarListResponse' - /calendar/{id}: + $ref: '#/components/schemas/CalendarListResponse' + + /calendar/{id}: get: tags: - Calendar - summary: Get one calendar event + summary: Get calendar event details + description: Retrieves details of a specific calendar event parameters: - name: id in: path required: true + description: Event ID schema: type: string responses: @@ -786,63 +1136,74 @@ paths: schema: $ref: '#/components/schemas/CalendarDetailResponse' - # Discussion - /discussion: + /discussion: get: tags: - Discussion - summary: Get discussions division + summary: Get division discussions + description: Retrieves a paginated list of discussions filtered by village and division parameters: - name: desa in: query required: true + description: Village ID schema: type: string - name: division in: query + description: Filter by division ID schema: type: string - name: status in: query + description: Filter by discussion status schema: type: string enum: [open, close] - name: active in: query + description: Filter by active status schema: type: boolean - name: search in: query + description: Search term for discussion description schema: type: string - name: page in: query + description: Page number for pagination schema: type: integer + minimum: 1 + default: 1 - name: get in: query + description: Number of items per page schema: type: integer + minimum: 1 + default: 10 responses: '200': description: List of discussions content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/DiscussionListResponse' + $ref: '#/components/schemas/DiscussionListResponse' - /discussion/{id}: + /discussion/{id}: get: tags: - Discussion - summary: Get one discussion division + summary: Get division discussion details + description: Retrieves details of a specific division discussion parameters: - name: id in: path required: true + description: Discussion ID schema: type: string responses: @@ -853,72 +1214,85 @@ paths: schema: $ref: '#/components/schemas/DiscussionDetailResponse' - # Discussion General - /discussion-general: + /discussion-general: get: tags: - DiscussionGeneral - summary: Get discussion general + summary: Get general discussions + description: Retrieves a paginated list of general discussions filtered by village and group parameters: - name: desa in: query required: true + description: Village ID schema: type: string - name: group in: query + description: Filter by group ID schema: type: string - name: search in: query + description: Search term for discussion title or description schema: type: string - name: status in: query + description: Filter by discussion status schema: type: string enum: [open, close] - name: active in: query + description: Filter by active status schema: type: boolean - name: page in: query + description: Page number for pagination schema: type: integer + minimum: 1 + default: 1 - name: get in: query + description: Number of items per page schema: type: integer + minimum: 1 + default: 10 responses: '200': - description: List of discussions + description: List of general discussions content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/DiskusiUmumListResponse' + $ref: '#/components/schemas/DiskusiUmumListResponse' - /discussion-general/{id}: + /discussion-general/{id}: get: tags: - DiscussionGeneral - summary: Get one discussion general + summary: Get general discussion details + description: Retrieves details of a specific general discussion, including members or comments based on category parameters: - name: id in: path required: true + description: Discussion ID schema: type: string - name: desa in: query required: true + description: Village ID schema: type: string - name: cat in: query + description: Category of data to retrieve schema: type: string enum: [detail, member, comment] @@ -933,63 +1307,74 @@ paths: - $ref: '#/components/schemas/DiskusiUmumMemberResponse' - $ref: '#/components/schemas/DiskusiUmumCommentResponse' - # Division - /division: + /division: get: tags: - Division summary: Get divisions + description: Retrieves a paginated list of divisions filtered by village and group parameters: - name: desa in: query required: true + description: Village ID schema: type: string - name: active in: query + description: Filter by active status schema: type: boolean - name: group in: query + description: Filter by group ID schema: type: string - name: search in: query + description: Search term for division name or description schema: type: string - name: page in: query + description: Page number for pagination schema: type: integer + minimum: 1 + default: 1 - name: get in: query + description: Number of items per page schema: type: integer + minimum: 1 + default: 10 responses: '200': description: List of divisions content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/DivisiListResponse' - - /division/{id}: + $ref: '#/components/schemas/DivisiListResponse' + + /division/{id}: get: tags: - Division - summary: Get one division detail + summary: Get division details + description: Retrieves details of a specific division parameters: - name: id in: path required: true + description: Division ID schema: type: string - name: desa in: query required: true + description: Village ID schema: type: string responses: @@ -1000,483 +1385,352 @@ paths: schema: $ref: '#/components/schemas/DivisiDetailResponse' - - # Home Data - /home: + # Document + /document: get: tags: - - Home - summary: Get home data by category + - Document + summary: Get documents + description: Retrieves a paginated list of documents and folders filtered by village and division parameters: - - name: user + - name: desa in: query required: true + description: Village ID schema: type: string - - name: cat + - name: division in: query - required: true + description: Filter by division ID schema: type: string - enum: [kegiatan, division, progress, dokumen, event, discussion, header, check-late-project] - responses: - '200': - description: Home data - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - - /home/search: - get: - tags: - - Home - summary: Search home - parameters: - - name: search + - name: path in: query - required: true - schema: - type: string - - name: user - in: query - required: true - schema: - type: string - responses: - '200': - description: Search results - content: - application/json: - schema: - type: array - items: - type: object - - /home/notification: - get: - tags: - - Notification - summary: Get notifications - parameters: - - name: user - in: query - required: true - schema: - type: string - - name: page - in: query - schema: - type: integer - responses: - '200': - description: List of notifications - content: - application/json: - schema: - type: array - items: - type: object - - # Group - /group: - get: - tags: - - Group - summary: Get groups - parameters: - - name: user - in: query - required: true + description: Filter by document path schema: type: string - name: active in: query - required: true + description: Filter by active status schema: - type: string + type: boolean - name: search in: query - required: true + description: Search term for document name schema: type: string + - name: page + in: query + description: Page number for pagination + schema: + type: integer + minimum: 1 + default: 1 + - name: get + in: query + description: Number of items per page + schema: + type: integer + minimum: 1 + default: 10 + responses: + '200': + description: List of documents and folders + content: + application/json: + schema: + $ref: '#/components/schemas/DocumentListResponse' + + # Group + /group: + get: + tags: + - Group + summary: Get groups + description: Retrieves a paginated list of groups filtered by village + parameters: + - name: desa + in: query + required: true + description: Village ID + schema: + type: string + - name: active + in: query + description: Filter by active status + schema: + type: boolean + - name: search + in: query + description: Search term for group name + schema: + type: string + - name: page + in: query + description: Page number for pagination + schema: + type: integer + minimum: 1 + default: 1 + - name: get + in: query + description: Number of items per page + schema: + type: integer + minimum: 1 + default: 10 responses: '200': description: List of groups content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Group' + $ref: '#/components/schemas/GroupListResponse' # Position - /position: + /position: get: tags: - Position summary: Get positions + description: Retrieves a paginated list of positions filtered by village and group parameters: - - name: user - in: query - required: true - schema: - type: string - - name: active + - name: desa in: query required: true + description: Village ID schema: type: string - name: group in: query + description: Filter by group ID schema: type: string - name: search in: query - required: true + description: Search term for position name schema: type: string + - name: active + in: query + description: Filter by active status + schema: + type: boolean + - name: page + in: query + description: Page number for pagination + schema: + type: integer + minimum: 1 + default: 1 + - name: get + in: query + description: Number of items per page + schema: + type: integer + minimum: 1 + default: 10 responses: '200': description: List of positions content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Position' - - # User - /user: - get: - tags: - - User - summary: Get users - parameters: - - name: user - in: query - required: true - schema: - type: string - - name: active - in: query - required: true - schema: - type: string - - name: group - in: query - schema: - type: string - - name: search - in: query - required: true - schema: - type: string - - name: page - in: query - schema: - type: integer - responses: - '200': - description: List of users - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/User' - - /user/{id}: - get: - tags: - - User - summary: Get profile - parameters: - - name: id - in: path - required: true - schema: - type: string - responses: - '200': - description: User profile - content: - application/json: - schema: - $ref: '#/components/schemas/User' + $ref: '#/components/schemas/PositionListResponse' # Project - /project: + /project: get: tags: - Project summary: Get projects + description: Retrieves a paginated list of projects filtered by village, group, and status parameters: - - name: user + - name: desa in: query required: true + description: Village ID schema: type: string - name: status in: query - required: true + description: Filter by project status schema: type: string + enum: [segera, dikerjakan, selesai, batal] - name: group in: query + description: Filter by group ID schema: type: string - name: search in: query - required: true - schema: - type: string - - name: cat - in: query + description: Search term for project title or description schema: type: string - name: page in: query + description: Page number for pagination schema: type: integer + minimum: 1 + default: 1 + - name: get + in: query + description: Number of items per page + schema: + type: integer + minimum: 1 + default: 10 responses: '200': description: List of projects content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Project' + $ref: '#/components/schemas/BaseResponseProjectList' - /project/{id}: + /project/{id}: get: tags: - Project - summary: Get one project + summary: Get project details + description: Retrieves details of a specific project based on category parameters: - name: id in: path required: true - schema: - type: string - - name: user - in: query - required: true + description: Project ID schema: type: string - name: cat in: query required: true + description: Category of project data to retrieve schema: type: string - enum: [data, progress, task, file, member, link] + enum: [data, task, file, member, link] responses: '200': description: Project details content: application/json: schema: - $ref: '#/components/schemas/Project' + oneOf: + - $ref: '#/components/schemas/BaseResponseProjectDetail' + - $ref: '#/components/schemas/BaseResponseTaskList' + - $ref: '#/components/schemas/BaseResponseMemberList' + - $ref: '#/components/schemas/BaseResponseFileList' + - $ref: '#/components/schemas/BaseResponseLinkList' # Task - /task: + /task: get: tags: - Task summary: Get tasks + description: Retrieves a paginated list of tasks filtered by village, division, and status parameters: - - name: user - in: query - required: true - schema: - type: string - - name: status + - name: desa in: query required: true + description: Village ID schema: type: string - name: division in: query - required: true + description: Filter by division ID schema: type: string + - name: status + in: query + description: Filter by task status + schema: + type: string + enum: [segera, dikerjakan, selesai, batal] - name: search in: query - required: true + description: Search term for task title or description schema: type: string - name: page in: query + description: Page number for pagination schema: type: integer + minimum: 1 + default: 1 + - name: get + in: query + description: Number of items per page + schema: + type: integer + minimum: 1 + default: 10 responses: '200': description: List of tasks content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Task' + $ref: '#/components/schemas/BaseResponseTaskList' - /task/{id}: + /task/{id}: get: tags: - Task - summary: Get one task + summary: Get task details + description: Retrieves details of a specific task based on category parameters: - name: id in: path required: true - schema: - type: string - - name: user - in: query - required: true + description: Task ID schema: type: string - name: cat in: query required: true + description: Category of task data to retrieve schema: type: string - enum: [data, progress, task, file, member, link] + enum: [data, task, file, member, link] responses: '200': description: Task details content: application/json: schema: - $ref: '#/components/schemas/Task' - - /task/detail/{id}: - get: - tags: - - Task - summary: Get task tugas - parameters: - - name: id - in: path - required: true - schema: - type: string - - name: user - in: query - required: true - schema: - type: string - - name: cat - in: query - schema: - type: string - responses: - '200': - description: Tugas details - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - - # Document - /document: - get: - tags: - - Document - summary: Get documents - parameters: - - name: user - in: query - required: true - schema: - type: string - - name: path - in: query - required: true - schema: - type: string - - name: division - in: query - required: true - schema: - type: string - - name: category - in: query - required: true - schema: - type: string - enum: [all, folder] - responses: - '200': - description: List of documents - content: - application/json: - schema: - type: array - items: - type: object - - /document/more: - get: - tags: - - Document - summary: Get document info - parameters: - - name: user - in: query - required: true - schema: - type: string - - name: item - in: query - required: true - schema: - type: string - - name: cat - in: query - required: true - schema: - type: string - enum: [share, lainnya] - responses: - '200': - description: Info - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - + oneOf: + - $ref: '#/components/schemas/BaseResponseTaskDetail' + - $ref: '#/components/schemas/BaseResponseSubTaskList' + - $ref: '#/components/schemas/BaseResponseMemberList' + - $ref: '#/components/schemas/BaseResponseFileList' + - $ref: '#/components/schemas/BaseResponseLinkList' security: - bearerAuth: [] tags: - name: Announcement - description: Announcements + description: Operations related to announcements - name: Banner - description: Banner management + description: Operations related to banner management - name: Calendar - description: Calendar events + description: Operations related to calendar events - name: Discussion - description: Division discussions + description: Operations related to division discussions - name: DiscussionGeneral - description: General discussions + description: Operations related to general discussions - name: Division - description: Division management - - - name: Home - description: Home and search - - name: Group - description: Group management - - name: Position - description: Position management - - name: User - description: User management - - name: Project - description: Project management - - name: Task - description: Task management + description: Operations related to division management - name: Document - description: Document management - - name: Notification - description: Notifications + description: Operations related to document management + - name: Group + description: Operations related to group management + - name: Position + description: Operations related to position management + - name: Project + description: Operations related to project management + - name: Task + description: Operations related to task management \ No newline at end of file From fdfa526d8c4428d87f64d2ad50bfab4e552d7521 Mon Sep 17 00:00:00 2001 From: amal Date: Mon, 22 Sep 2025 11:43:26 +0800 Subject: [PATCH 17/21] upd: api ai user Deskripsi: - list user - detail user No Issues --- src/app/api/ai/user/[id]/route.ts | 75 ++++++++++++++++++++++++++ src/app/api/ai/user/route.ts | 88 +++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 src/app/api/ai/user/[id]/route.ts create mode 100644 src/app/api/ai/user/route.ts diff --git a/src/app/api/ai/user/[id]/route.ts b/src/app/api/ai/user/[id]/route.ts new file mode 100644 index 0000000..bb8048c --- /dev/null +++ b/src/app/api/ai/user/[id]/route.ts @@ -0,0 +1,75 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + +// GET ONE MEMBER / USER +export async function GET(request: Request, context: { params: { id: string } }) { + try { + const { id } = context.params; + + const users = await prisma.user.findUnique({ + where: { + id: 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 + }, + }, + }, + }); + + const { ...userData } = users; + const group = users?.Group.name + const position = users?.Position?.name + const idUserRole = users?.UserRole.id + const phone = '+62' + users?.phone + const role = users?.UserRole.name + const gender = users?.gender == "F" ? "Perempuan" : "Laki-Laki" + + const result = { ...userData, gender, group, position, idUserRole, phone, role }; + + const omitData = _.omit(result, ["Group", "Position", "UserRole"]); + + + + return NextResponse.json( + { + success: true, + message: "Berhasil mendapatkan anggota", + data: omitData, + }, + { status: 200 } + ); + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan anggota, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/ai/user/route.ts b/src/app/api/ai/user/route.ts new file mode 100644 index 0000000..0f5908e --- /dev/null +++ b/src/app/api/ai/user/route.ts @@ -0,0 +1,88 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + +// GET ALL MEMBER / USER +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url); + const search = searchParams.get('search') + const idVillage = searchParams.get("desa"); + const idGroup = searchParams.get("group"); + const active = searchParams.get("active"); + const page = searchParams.get('page'); + const get = searchParams.get('get'); + + let getFix = 10; + if (get == null || get == undefined || get == "" || _.isNaN(Number(get))) { + getFix = 10; + } else { + getFix = Number(get); + } + + const dataSkip = page == null || page == undefined ? 0 : Number(page) * getFix - getFix; + + let kondisi: any = { + isActive: active == 'false' ? false : true, + idVillage: String(idVillage), + name: { + contains: (search == undefined || search == null) ? "" : search, + mode: "insensitive", + }, + NOT: { + idUserRole: 'developer' + } + } + + if (idGroup != "null" && idGroup != undefined && idGroup != "" && idGroup != null) { + kondisi = { + ...kondisi, + idGroup: String(idGroup) + } + } + + + 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 allData = 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 NextResponse.json({ success: true, message: "Berhasil member", data: allData }, { status: 200 }); + + + + } catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan anggota, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file From 4b1d2e28dc84d9a4b030df389d4d34f63e1b0e92 Mon Sep 17 00:00:00 2001 From: amal Date: Mon, 22 Sep 2025 15:31:02 +0800 Subject: [PATCH 18/21] upd: api ai report divisi Deskripsi: - update api ai laporan divisi NO Issues --- src/app/api/ai/division/report/route.ts | 269 ++++++++++++++++++++++++ 1 file changed, 269 insertions(+) create mode 100644 src/app/api/ai/division/report/route.ts diff --git a/src/app/api/ai/division/report/route.ts b/src/app/api/ai/division/report/route.ts new file mode 100644 index 0000000..2d6bbee --- /dev/null +++ b/src/app/api/ai/division/report/route.ts @@ -0,0 +1,269 @@ +import { prisma } from "@/module/_global"; +import _ from "lodash"; +import { NextResponse } from "next/server"; + +export async function GET(request: Request) { + try { + const { searchParams } = new URL(request.url) + const idVillage = searchParams.get("desa") + const idGroup = searchParams.get("group") + const division = searchParams.get("division") + const date = searchParams.get("date-start") + const dateAkhir = searchParams.get("date-end") + const kat = searchParams.get("cat") + + + // CHART PROGRESS + if (kat == "dokumen") { + // CHART DOKUMEN + let kondisi: any = { + isActive: true, + category: 'FILE', + Division: { + idVillage: String(idVillage) + }, + createdAt: { + gte: new Date(String(date)), + lte: new Date(String(dateAkhir)) + }, + } + + if (idGroup != undefined && idGroup != null && idGroup != "") { + kondisi = { + isActive: true, + category: 'FILE', + Division: { + idGroup: String(idGroup) + }, + createdAt: { + gte: new Date(String(date)), + lte: new Date(String(dateAkhir)) + }, + } + } + + + if (division != undefined && division != null && division != "") { + kondisi = { + ...kondisi, + idDivision: String(division) + } + } + + + const dataDokumen = await prisma.divisionDocumentFolderFile.findMany({ + where: kondisi, + }) + + const groupData = _.map(_.groupBy(dataDokumen, "extension"), (v: any) => ({ + file: v[0].extension, + jumlah: v.length, + })) + + const image = ['jpg', 'jpeg', 'png', 'heic'] + + let hasilImage = { + name: 'Gambar', + value: 0 + } + + let hasilFile = { + name: 'Dokumen', + value: 0 + } + + groupData.map((v: any) => { + if (image.some((i: any) => i == v.file)) { + hasilImage = { + name: 'Gambar', + value: hasilImage.value + v.jumlah + } + } else { + hasilFile = { + name: 'Dokumen', + value: hasilFile.value + v.jumlah + } + } + }) + + const hasilDokumen = { gambar: hasilImage.value, dokumen: hasilFile.value } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan data", data: hasilDokumen }, { status: 200 }); + } else if (kat == "event") { + // CHART EVENT + let kondisiSelesai: any = { + isActive: true, + Division: { + idVillage: String(idVillage) + }, + DivisionCalendarReminder: { + some: { + dateStart: { + gte: new Date(String(date)), + lte: new Date() + } + } + } + } + + let kondisiComingSoon: any = { + isActive: true, + Division: { + idVillage: String(idVillage) + }, + DivisionCalendarReminder: { + some: { + dateStart: { + gt: new Date(), + lte: new Date(String(dateAkhir)) + } + } + } + } + + if (idGroup != undefined && idGroup != null && idGroup != "") { + kondisiSelesai = { + isActive: true, + Division: { + idGroup: String(idGroup) + }, + DivisionCalendarReminder: { + some: { + dateStart: { + gte: new Date(String(date)), + lte: new Date() + } + } + } + } + + kondisiComingSoon = { + isActive: true, + Division: { + idGroup: String(idGroup) + }, + DivisionCalendarReminder: { + some: { + dateStart: { + gt: new Date(), + lte: new Date(String(dateAkhir)) + } + } + } + } + } + + if (division != undefined && division != null && division != "") { + kondisiSelesai = { + ...kondisiSelesai, + idDivision: String(division) + } + + kondisiComingSoon = { + ...kondisiComingSoon, + idDivision: String(division) + } + } + + const eventSelesai = await prisma.divisionCalendar.count({ + where: kondisiSelesai + }) + + const eventComingSoon = await prisma.divisionCalendar.count({ + where: kondisiComingSoon + }) + + const hasilEvent = { + selesai: eventSelesai, + akan_datang: eventComingSoon + } + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan data", data: hasilEvent }, { status: 200 }); + + } else { + let kondisiProgress: any = { + isActive: true, + Division: { + idVillage: String(idVillage) + }, + DivisionProjectTask: { + some: { + dateStart: { + gte: new Date(String(date)) + }, + dateEnd: { + lte: new Date(String(dateAkhir)) + } + } + } + } + + if (idGroup != undefined && idGroup != null && idGroup != "") { + kondisiProgress = { + isActive: true, + Division: { + idGroup: String(idGroup) + }, + DivisionProjectTask: { + some: { + dateStart: { + gte: new Date(String(date)) + }, + dateEnd: { + lte: new Date(String(dateAkhir)) + } + } + } + } + } + + if (division != undefined && division != null && division != "") { + kondisiProgress = { + ...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 hasilProgres: any[] = [] + let input + for (let index = 0; index < dataStatus.length; index++) { + const cek = data.some((i: any) => i.status == dataStatus[index].status) + if (cek) { + const find = ((Number(data.find((i: any) => i.status == dataStatus[index].status)?._count) * 100) / data.reduce((n, { _count }) => n + _count, 0)).toFixed(2) + const fix = find != "100.00" ? find.substr(-2, 2) == "00" ? find.substr(0, 2) : find : "100" + input = { + name: dataStatus[index].name, + value: fix + } + } else { + input = { + name: dataStatus[index].name, + value: 0 + } + } + hasilProgres.push(input) + } + + const dataFixProgress = hasilProgres.reduce((acc: any, curr: any) => { + acc[curr.name] = curr.value + return acc + }, {}) + + + return NextResponse.json({ success: true, message: "Berhasil mendapatkan data", data: dataFixProgress }, { status: 200 }); + + } + + } + catch (error) { + console.error(error); + return NextResponse.json({ success: false, message: "Gagal mendapatkan data, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 }); + } +} \ No newline at end of file From 9c07375015645da1b7bf685f5619afb4609ddc9c Mon Sep 17 00:00:00 2001 From: amal Date: Mon, 22 Sep 2025 15:31:23 +0800 Subject: [PATCH 19/21] upd: swagger api ai --- darmasaba-api-ai.yml | 1380 +++++++++++++++++++++++++++++++++--------- 1 file changed, 1079 insertions(+), 301 deletions(-) diff --git a/darmasaba-api-ai.yml b/darmasaba-api-ai.yml index 43ca383..cba55c6 100644 --- a/darmasaba-api-ai.yml +++ b/darmasaba-api-ai.yml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: - title: API AI Desa+ - description: API untuk sistem manajemen AI Desa+ + title: AI Desa+ API + description: API for the AI Desa+ management system, providing endpoints for managing announcements, banners, calendar events, discussions, divisions, documents, groups, positions, projects, tasks, users, and division reports. version: 1.0.0 contact: name: API Support @@ -9,6 +9,8 @@ info: servers: - url: http://localhost:3000/api/ai + description: Development server + - url: https://api.desa-plus.com/api/ai description: Production server components: @@ -27,17 +29,23 @@ components: properties: success: type: boolean + description: Indicates whether the request was successful message: type: string + description: Response message or error description meta: type: object properties: total: type: integer + description: Total number of items page: type: integer - perPage: + description: Current page number + get: type: integer + description: Number of items per page + description: Pagination metadata # Banner BannerBase: @@ -51,40 +59,51 @@ components: properties: id: type: string + description: Unique identifier for the banner idVillage: type: string + description: ID of the village associated with the banner title: type: string - extension: - type: string + description: Title of the banner image: type: string + description: URL or path to the banner image + extension: + type: string + description: File extension of the banner image (e.g., jpg, png) + nullable: true isActive: type: boolean + description: Indicates whether the banner is active createdAt: type: string format: date-time + description: Timestamp when the banner was created updatedAt: type: string format: date-time + description: Timestamp when the banner was last updated BannerListResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - $ref: '#/components/schemas/BannerBase' + $ref: "#/components/schemas/BannerBase" + description: List of banners BannerDetailResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: - $ref: '#/components/schemas/BannerBase' + $ref: "#/components/schemas/BannerBase" + description: Details of a specific banner # Announcement AnnouncementBase: @@ -98,22 +117,30 @@ components: properties: id: type: string + description: Unique identifier for the announcement idVillage: type: string + description: ID of the village associated with the announcement title: type: string + description: Title of the announcement desc: type: string + description: Description or content of the announcement isActive: type: boolean + description: Indicates whether the announcement is active createdBy: type: string + description: ID of the user who created the announcement createdAt: type: string format: date-time + description: Timestamp when the announcement was created updatedAt: type: string format: date-time + description: Timestamp when the announcement was last updated AnnouncementMember: type: object @@ -123,26 +150,31 @@ components: properties: idGroup: type: string + description: ID of the group associated with the announcement idDivision: type: string + description: ID of the division associated with the announcement group: type: string + description: Name of the group division: type: string + description: Name of the division AnnouncementListResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - $ref: '#/components/schemas/AnnouncementBase' + $ref: "#/components/schemas/AnnouncementBase" + description: List of announcements AnnouncementDetailResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -150,14 +182,33 @@ components: properties: id: type: string + description: Unique identifier for the announcement title: type: string + description: Title of the announcement desc: type: string + description: Description or content of the announcement + isActive: + type: boolean + description: Indicates whether the announcement is active + createdBy: + type: string + description: ID of the user who created the announcement + createdAt: + type: string + format: date-time + description: Timestamp when the announcement was created + updatedAt: + type: string + format: date-time + description: Timestamp when the announcement was last updated member: type: array items: - $ref: '#/components/schemas/AnnouncementMember' + $ref: "#/components/schemas/AnnouncementMember" + description: List of members associated with the announcement + description: Details of a specific announcement # Calendar CalendarBase: @@ -170,24 +221,40 @@ components: properties: id: type: string + description: Unique identifier for the calendar event dateStart: type: string format: date-time + description: Start date and time of the event timeStart: type: string + description: Start time of the event (e.g., HH:MM) + nullable: true timeEnd: type: string + description: End time of the event (e.g., HH:MM) + nullable: true + title: + type: string + description: Title of the event + desc: + type: string + description: Description of the event + nullable: true + createdBy: + type: string + description: ID of the user who created the event + isActive: + type: boolean + description: Indicates whether the event is active createdAt: type: string format: date-time - title: + description: Timestamp when the event was created + updatedAt: type: string - desc: - type: string - createdBy: - type: string - isActive: - type: boolean + format: date-time + description: Timestamp when the event was last updated CalendarMember: type: object @@ -199,29 +266,35 @@ components: properties: id: type: string + description: Unique identifier for the member idUser: type: string + description: ID of the user name: type: string + description: Name of the user email: type: string + description: Email of the user img: type: string + description: URL or path to the user's image nullable: true CalendarListResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - $ref: '#/components/schemas/CalendarBase' + $ref: "#/components/schemas/CalendarBase" + description: List of calendar events CalendarDetailResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -229,30 +302,58 @@ components: properties: id: type: string - timeStart: - type: string + description: Unique identifier for the event dateStart: type: string format: date-time + description: Start date and time of the event + timeStart: + type: string + description: Start time of the event (e.g., HH:MM) + nullable: true timeEnd: type: string + description: End time of the event (e.g., HH:MM) + nullable: true + title: + type: string + description: Title of the event + desc: + type: string + description: Description of the event + nullable: true + linkMeet: + type: string + description: URL for the meeting (if applicable) + nullable: true + repeatEventType: + type: string + description: Type of event repetition (e.g., daily, weekly) + nullable: true + repeatValue: + type: integer + description: Number of repetitions or interval + nullable: true + createdBy: + type: string + description: ID of the user who created the event + isActive: + type: boolean + description: Indicates whether the event is active createdAt: type: string format: date-time - title: + description: Timestamp when the event was created + updatedAt: type: string - desc: - type: string - linkMeet: - type: string - repeatEventType: - type: string - repeatValue: - type: integer + format: date-time + description: Timestamp when the event was last updated member: type: array items: - $ref: '#/components/schemas/CalendarMember' + $ref: "#/components/schemas/CalendarMember" + description: List of members associated with the event + description: Details of a specific calendar event # Discussion DiscussionBase: @@ -265,20 +366,31 @@ components: properties: id: type: string + description: Unique identifier for the discussion desc: type: string + description: Description of the discussion + idDivision: + type: string + description: ID of the division associated with the discussion + division: + type: string + description: Name of the division + status: + type: string + enum: [open, closed] + description: Status of the discussion + totalKomentar: + type: integer + description: Total number of comments in the discussion createdAt: type: string format: date-time - idDivision: + description: Timestamp when the discussion was created + updatedAt: type: string - division: - type: string - totalKomentar: - type: integer - status: - type: string - enum: [open, close] + format: date-time + description: Timestamp when the discussion was last updated DiscussionComment: type: object @@ -289,30 +401,36 @@ components: properties: id: type: string + description: Unique identifier for the comment comment: type: string + description: Content of the comment + username: + type: string + description: Username of the commenter + userimg: + type: string + description: URL or path to the commenter's image + nullable: true createdAt: type: string format: date-time - username: - type: string - userimg: - type: string - nullable: true + description: Timestamp when the comment was created DiscussionListResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - $ref: '#/components/schemas/DiscussionBase' + $ref: "#/components/schemas/DiscussionBase" + description: List of discussions DiscussionDetailResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -320,63 +438,92 @@ components: properties: id: type: string + description: Unique identifier for the discussion idDivision: type: string + description: ID of the division associated with the discussion division: type: string - isActive: - type: boolean + description: Name of the division desc: type: string + description: Description of the discussion status: type: string - enum: [open, close] + enum: [open, closed] + description: Status of the discussion + isActive: + type: boolean + description: Indicates whether the discussion is active + createdBy: + type: string + description: ID of the user who created the discussion createdAt: type: string format: date-time - createdBy: + description: Timestamp when the discussion was created + updatedAt: type: string + format: date-time + description: Timestamp when the discussion was last updated komentar: type: array items: - $ref: '#/components/schemas/DiscussionComment' + $ref: "#/components/schemas/DiscussionComment" + description: List of comments in the discussion + description: Details of a specific discussion # Discussion General + DiskusiUmumBase: + type: object + required: + - id + - title + - desc + - status + properties: + id: + type: string + description: Unique identifier for the general discussion + title: + type: string + description: Title of the general discussion + desc: + type: string + description: Description of the general discussion + status: + type: string + enum: [open, closed] + description: Status of the general discussion + group: + type: string + description: Name of the group associated with the discussion + totalKomentar: + type: integer + description: Total number of comments in the discussion + createdAt: + type: string + format: date-time + description: Timestamp when the discussion was created + updatedAt: + type: string + format: date-time + description: Timestamp when the discussion was last updated + DiskusiUmumListResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - type: object - required: - - id - - title - - desc - - status - properties: - id: - type: string - title: - type: string - desc: - type: string - createdAt: - type: string - format: date-time - totalKomentar: - type: integer - status: - type: string - enum: [open, close] - group: - type: string + $ref: "#/components/schemas/DiskusiUmumBase" + description: List of general discussions DiskusiUmumDetailResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -389,26 +536,39 @@ components: properties: id: type: string - isActive: - type: boolean + description: Unique identifier for the general discussion idGroup: type: string + description: ID of the group associated with the discussion group: type: string + description: Name of the group title: type: string + description: Title of the general discussion desc: type: string + description: Description of the general discussion status: type: string - enum: [open, close] + enum: [open, closed] + description: Status of the general discussion + isActive: + type: boolean + description: Indicates whether the discussion is active createdAt: type: string format: date-time + description: Timestamp when the discussion was created + updatedAt: + type: string + format: date-time + description: Timestamp when the discussion was last updated + description: Details of a specific general discussion DiskusiUmumMemberResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -421,15 +581,19 @@ components: properties: idUser: type: string + description: ID of the user name: type: string + description: Name of the user img: type: string + description: URL or path to the user's image nullable: true + description: List of members in the general discussion DiskusiUmumCommentResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -444,49 +608,67 @@ components: properties: id: type: string + description: Unique identifier for the comment comment: type: string + description: Content of the comment + idUser: + type: string + description: ID of the user who made the comment + username: + type: string + description: Username of the commenter + img: + type: string + description: URL or path to the commenter's image + nullable: true createdAt: type: string format: date-time - idUser: - type: string - username: - type: string - img: - type: string - nullable: true + description: Timestamp when the comment was created + description: List of comments in the general discussion # Division + DivisiBase: + type: object + required: + - id + - name + properties: + id: + type: string + description: Unique identifier for the division + name: + type: string + description: Name of the division + desc: + type: string + description: Description of the division + nullable: true + idGroup: + type: string + description: ID of the group associated with the division + group: + type: string + description: Name of the group + jumlahMember: + type: integer + description: Number of members in the division + DivisiListResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - type: object - required: - - id - - name - properties: - id: - type: string - name: - type: string - desc: - type: string - idGroup: - type: string - group: - type: string - jumlahMember: - type: integer + $ref: "#/components/schemas/DivisiBase" + description: List of divisions DivisiDetailResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -498,24 +680,34 @@ components: properties: id: type: string + description: Unique identifier for the division idVillage: type: string + description: ID of the village associated with the division idGroup: type: string + description: ID of the group associated with the division name: type: string + description: Name of the division desc: type: string + description: Description of the division + nullable: true isActive: type: boolean + description: Indicates whether the division is active createdBy: type: string + description: ID of the user who created the division createdAt: type: string format: date-time + description: Timestamp when the division was created updatedAt: type: string format: date-time + description: Timestamp when the division was last updated member: type: array items: @@ -527,15 +719,76 @@ components: properties: id: type: string - isAdmin: - type: boolean + description: Unique identifier for the member idUser: type: string + description: ID of the user name: type: string + description: Name of the user + isAdmin: + type: boolean + description: Indicates whether the user is an admin img: type: string + description: URL or path to the user's image nullable: true + description: List of members in the division + description: Details of a specific division + + DivisiReportDokumenResponse: + allOf: + - $ref: "#/components/schemas/BaseResponse" + - type: object + properties: + data: + type: object + properties: + gambar: + type: integer + description: Number of images in the division + dokumen: + type: integer + description: Number of documents in the division + description: Document report for the division + + DivisiReportEventResponse: + allOf: + - $ref: "#/components/schemas/BaseResponse" + - type: object + properties: + data: + type: object + properties: + selesai: + type: integer + description: Number of completed events + akan_datang: + type: integer + description: Number of upcoming events + description: Event report for the division + + DivisiReportProgressResponse: + allOf: + - $ref: "#/components/schemas/BaseResponse" + - type: object + properties: + data: + type: object + properties: + Segera: + type: string + description: Percentage of tasks in 'Segera' status + Dikerjakan: + type: string + description: Percentage of tasks in 'Dikerjakan' status + Selesai: + type: integer + description: Percentage of tasks in 'Selesai' status + Dibatalkan: + type: string + description: Percentage of tasks in 'Dibatalkan' status + description: Progress report for the division # Document DocumentItem: @@ -548,38 +801,50 @@ components: properties: id: type: string + description: Unique identifier for the document or folder category: type: string enum: [FILE, FOLDER] + description: Type of item (file or folder) name: type: string + description: Name of the document or folder extension: type: string + description: File extension (e.g., pdf, docx) + nullable: true idStorage: type: string + description: ID of the storage location nullable: true path: type: string + description: Path to the document or folder createdBy: type: string + description: ID of the user who created the document or folder createdAt: type: string format: date-time + description: Timestamp when the document or folder was created updatedAt: type: string format: date-time + description: Timestamp when the document or folder was last updated share: type: boolean + description: Indicates whether the document or folder is shared DocumentListResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - $ref: '#/components/schemas/DocumentItem' + $ref: "#/components/schemas/DocumentItem" + description: List of documents and folders # Group GroupItem: @@ -592,28 +857,35 @@ components: properties: id: type: string + description: Unique identifier for the group idVillage: type: string + description: ID of the village associated with the group name: type: string + description: Name of the group isActive: type: boolean + description: Indicates whether the group is active createdAt: type: string format: date-time + description: Timestamp when the group was created updatedAt: type: string format: date-time + description: Timestamp when the group was last updated GroupListResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - $ref: '#/components/schemas/GroupItem' + $ref: "#/components/schemas/GroupItem" + description: List of groups # Position PositionItem: @@ -626,69 +898,89 @@ components: properties: id: type: string + description: Unique identifier for the position name: type: string + description: Name of the position idGroup: type: string + description: ID of the group associated with the position group: type: string + description: Name of the group isActive: type: boolean + description: Indicates whether the position is active createdAt: type: string format: date-time + description: Timestamp when the position was created updatedAt: type: string format: date-time + description: Timestamp when the position was last updated PositionListResponse: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - $ref: '#/components/schemas/PositionItem' + $ref: "#/components/schemas/PositionItem" + description: List of positions # Project + ProjectBase: + type: object + required: + - id + - idGroup + - title + - status + properties: + id: + type: string + description: Unique identifier for the project + idGroup: + type: string + description: ID of the group associated with the project + title: + type: string + description: Title of the project + desc: + type: string + description: Description of the project + nullable: true + group: + type: string + description: Name of the group + status: + type: string + enum: [segera, dikerjakan, selesai, batal] + description: Status of the project + progress: + type: integer + description: Progress percentage of the project + member: + type: integer + description: Number of members in the project + BaseResponseProjectList: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - type: object - required: - - id - - idGroup - - title - - status - properties: - id: - type: string - idGroup: - type: string - title: - type: string - desc: - type: string - nullable: true - group: - type: string - status: - type: string - enum: [segera, dikerjakan, selesai, batal] - progress: - type: integer - member: - type: integer + $ref: "#/components/schemas/ProjectBase" + description: List of projects BaseResponseProjectDetail: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -703,68 +995,101 @@ components: properties: id: type: string + description: Unique identifier for the project idVillage: type: string + description: ID of the village associated with the project idGroup: type: string + description: ID of the group associated with the project group: type: string + description: Name of the group title: type: string + description: Title of the project status: type: string enum: [segera, dikerjakan, selesai, batal] + description: Status of the project desc: type: string + description: Description of the project nullable: true reason: type: string + description: Reason for project status (e.g., cancellation) nullable: true report: type: string + description: Project report or summary nullable: true isActive: type: boolean + description: Indicates whether the project is active progress: type: integer + description: Progress percentage of the project + createdAt: + type: string + format: date-time + description: Timestamp when the project was created + updatedAt: + type: string + format: date-time + description: Timestamp when the project was last updated + description: Details of a specific project + + # Task + TaskBase: + type: object + required: + - id + - idDivision + - title + - status + properties: + id: + type: string + description: Unique identifier for the task + idDivision: + type: string + description: ID of the division associated with the task + title: + type: string + description: Title of the task + desc: + type: string + description: Description of the task + nullable: true + division: + type: string + description: Name of the division + status: + type: string + enum: [segera, dikerjakan, selesai, batal] + description: Status of the task + progress: + type: integer + description: Progress percentage of the task + member: + type: integer + description: Number of members assigned to the task BaseResponseTaskList: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - type: object - required: - - id - - idDivision - - title - - status - properties: - id: - type: string - idDivision: - type: string - title: - type: string - desc: - type: string - nullable: true - division: - type: string - status: - type: string - enum: [segera, dikerjakan, selesai, batal] - progress: - type: integer - member: - type: integer + $ref: "#/components/schemas/TaskBase" + description: List of tasks BaseResponseTaskDetail: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -778,31 +1103,51 @@ components: properties: id: type: string + description: Unique identifier for the task idDivision: type: string + description: ID of the division associated with the task division: type: string + description: Name of the division title: type: string + description: Title of the task status: type: string + enum: [segera, dikerjakan, selesai, batal] + description: Status of the task desc: type: string + description: Description of the task nullable: true reason: type: string + description: Reason for task status (e.g., cancellation) nullable: true report: type: string + description: Task report or summary nullable: true isActive: type: boolean + description: Indicates whether the task is active progress: type: integer + description: Progress percentage of the task + createdAt: + type: string + format: date-time + description: Timestamp when the task was created + updatedAt: + type: string + format: date-time + description: Timestamp when the task was last updated + description: Details of a specific task BaseResponseSubTaskList: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -816,21 +1161,27 @@ components: properties: id: type: string + description: Unique identifier for the subtask title: type: string + description: Title of the subtask status: type: string - enum: [belum selesai, selesai] + enum: [belum_selesai, selesai] + description: Status of the subtask dateStart: type: string format: date-time + description: Start date and time of the subtask dateEnd: type: string format: date-time + description: End date and time of the subtask + description: List of subtasks BaseResponseMemberList: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -845,57 +1196,40 @@ components: properties: id: type: string + description: Unique identifier for the member idUser: type: string + description: ID of the user name: type: string + description: Name of the user email: type: string + description: Email of the user img: type: string + description: URL or path to the user's image nullable: true position: type: string + description: Position of the user + nullable: true + description: List of members BaseResponseFileList: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: type: array items: - type: object - required: - - id - - name - - path - properties: - id: - type: string - name: - type: string - extension: - type: string - idStorage: - type: string - nullable: true - path: - type: string - createdBy: - type: string - createdAt: - type: string - format: date-time - updatedAt: - type: string - format: date-time - share: - type: boolean + $ref: "#/components/schemas/DocumentItem" + description: List of files BaseResponseLinkList: allOf: - - $ref: '#/components/schemas/BaseResponse' + - $ref: "#/components/schemas/BaseResponse" - type: object properties: data: @@ -910,18 +1244,153 @@ components: properties: id: type: string + description: Unique identifier for the link idProject: type: string + description: ID of the project associated with the link link: type: string + description: URL of the link isActive: type: boolean + description: Indicates whether the link is active createdAt: type: string format: date-time + description: Timestamp when the link was created updatedAt: type: string format: date-time + description: Timestamp when the link was last updated + description: List of links + + # User + UserBase: + type: object + required: + - id + - idUserRole + - nik + - name + - gender + - phone + - isActive + properties: + id: + type: string + description: Unique identifier for the user + idUserRole: + type: string + description: ID of the user's role + nik: + type: string + description: National identification number of the user + name: + type: string + description: Name of the user + gender: + type: string + description: Gender of the user + phone: + type: string + description: Phone number of the user + position: + type: string + description: Position of the user + nullable: true + group: + type: string + description: Name of the group the user belongs to + nullable: true + isActive: + type: boolean + description: Indicates whether the user is active + + UserListResponse: + allOf: + - $ref: "#/components/schemas/BaseResponse" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/UserBase" + description: List of users + + UserDetailResponse: + allOf: + - $ref: "#/components/schemas/BaseResponse" + - type: object + properties: + data: + type: object + required: + - id + - idUserRole + - nik + - name + - phone + - email + - gender + - isActive + properties: + id: + type: string + description: Unique identifier for the user + idUserRole: + type: string + description: ID of the user's role + nik: + type: string + description: National identification number of the user + name: + type: string + description: Name of the user + email: + type: string + description: Email of the user + gender: + type: string + description: Gender of the user + phone: + type: string + description: Phone number of the user + img: + type: string + description: URL or path to the user's image + nullable: true + idGroup: + type: string + description: ID of the group the user belongs to + nullable: true + idPosition: + type: string + description: ID of the user's position + nullable: true + role: + type: string + description: Role of the user + nullable: true + position: + type: string + description: Position of the user + nullable: true + group: + type: string + description: Name of the group the user belongs to + nullable: true + isActive: + type: boolean + description: Indicates whether the user is active + createdAt: + type: string + format: date-time + description: Timestamp when the user was created + updatedAt: + type: string + format: date-time + description: Timestamp when the user was last updated + description: Details of a specific user paths: # Announcement @@ -930,7 +1399,7 @@ paths: tags: - Announcement summary: Get list of announcements - description: Retrieves a paginated list of announcements filtered by village + description: Retrieves a paginated list of announcements filtered by village, search term, and active status parameters: - name: desa in: query @@ -943,6 +1412,7 @@ paths: description: Search term for announcement title or description schema: type: string + nullable: true - name: page in: query description: Page number for pagination @@ -950,7 +1420,7 @@ paths: type: integer minimum: 1 default: 1 - - name: get + - name: perPage in: query description: Number of items per page schema: @@ -962,13 +1432,20 @@ paths: description: Filter by active status schema: type: boolean + nullable: true responses: - '200': + "200": description: List of announcements content: application/json: schema: - $ref: '#/components/schemas/AnnouncementListResponse' + $ref: "#/components/schemas/AnnouncementListResponse" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error /announcement/{id}: get: @@ -984,12 +1461,18 @@ paths: schema: type: string responses: - '200': + "200": description: Announcement details content: application/json: schema: - $ref: '#/components/schemas/AnnouncementDetailResponse' + $ref: "#/components/schemas/AnnouncementDetailResponse" + "404": + description: Announcement not found + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error # Banner /banner: @@ -997,7 +1480,7 @@ paths: tags: - Banner summary: Get list of banners - description: Retrieves a paginated list of banners filtered by village + description: Retrieves a paginated list of banners filtered by village, search term, and active status parameters: - name: desa in: query @@ -1010,6 +1493,7 @@ paths: description: Search term for banner title schema: type: string + nullable: true - name: page in: query description: Page number for pagination @@ -1017,7 +1501,7 @@ paths: type: integer minimum: 1 default: 1 - - name: get + - name: perPage in: query description: Number of items per page schema: @@ -1029,13 +1513,20 @@ paths: description: Filter by active status schema: type: boolean + nullable: true responses: - '200': + "200": description: List of banners content: application/json: schema: - $ref: '#/components/schemas/BannerListResponse' + $ref: "#/components/schemas/BannerListResponse" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error /banner/{id}: get: @@ -1051,12 +1542,18 @@ paths: schema: type: string responses: - '200': + "200": description: Banner details content: application/json: schema: - $ref: '#/components/schemas/BannerDetailResponse' + $ref: "#/components/schemas/BannerDetailResponse" + "404": + description: Banner not found + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error # Calendar /calendar: @@ -1064,7 +1561,7 @@ paths: tags: - Calendar summary: Get calendar events - description: Retrieves a paginated list of calendar events filtered by village, date, and division + description: Retrieves a paginated list of calendar events filtered by village, date, division, and active status parameters: - name: desa in: query @@ -1078,21 +1575,25 @@ paths: schema: type: string format: date + nullable: true - name: division in: query description: Filter by division ID schema: type: string + nullable: true - name: active in: query description: Filter by active status schema: type: boolean + nullable: true - name: search in: query description: Search term for event title or description schema: type: string + nullable: true - name: page in: query description: Page number for pagination @@ -1100,7 +1601,7 @@ paths: type: integer minimum: 1 default: 1 - - name: get + - name: perPage in: query description: Number of items per page schema: @@ -1108,12 +1609,18 @@ paths: minimum: 1 default: 10 responses: - '200': + "200": description: List of calendar events content: application/json: schema: - $ref: '#/components/schemas/CalendarListResponse' + $ref: "#/components/schemas/CalendarListResponse" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error /calendar/{id}: get: @@ -1129,12 +1636,18 @@ paths: schema: type: string responses: - '200': + "200": description: Event details content: application/json: schema: - $ref: '#/components/schemas/CalendarDetailResponse' + $ref: "#/components/schemas/CalendarDetailResponse" + "404": + description: Event not found + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error # Discussion /discussion: @@ -1142,7 +1655,7 @@ paths: tags: - Discussion summary: Get division discussions - description: Retrieves a paginated list of discussions filtered by village and division + description: Retrieves a paginated list of discussions filtered by village, division, status, and active status parameters: - name: desa in: query @@ -1155,22 +1668,26 @@ paths: description: Filter by division ID schema: type: string + nullable: true - name: status in: query description: Filter by discussion status schema: type: string - enum: [open, close] + enum: [open, closed] + nullable: true - name: active in: query description: Filter by active status schema: type: boolean + nullable: true - name: search in: query description: Search term for discussion description schema: type: string + nullable: true - name: page in: query description: Page number for pagination @@ -1178,7 +1695,7 @@ paths: type: integer minimum: 1 default: 1 - - name: get + - name: perPage in: query description: Number of items per page schema: @@ -1186,12 +1703,18 @@ paths: minimum: 1 default: 10 responses: - '200': + "200": description: List of discussions content: application/json: schema: - $ref: '#/components/schemas/DiscussionListResponse' + $ref: "#/components/schemas/DiscussionListResponse" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error /discussion/{id}: get: @@ -1207,12 +1730,18 @@ paths: schema: type: string responses: - '200': + "200": description: Discussion details content: application/json: schema: - $ref: '#/components/schemas/DiscussionDetailResponse' + $ref: "#/components/schemas/DiscussionDetailResponse" + "404": + description: Discussion not found + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error # Discussion General /discussion-general: @@ -1220,7 +1749,7 @@ paths: tags: - DiscussionGeneral summary: Get general discussions - description: Retrieves a paginated list of general discussions filtered by village and group + description: Retrieves a paginated list of general discussions filtered by village, group, status, and active status parameters: - name: desa in: query @@ -1233,22 +1762,26 @@ paths: description: Filter by group ID schema: type: string + nullable: true - name: search in: query description: Search term for discussion title or description schema: type: string + nullable: true - name: status in: query description: Filter by discussion status schema: type: string - enum: [open, close] + enum: [open, closed] + nullable: true - name: active in: query description: Filter by active status schema: type: boolean + nullable: true - name: page in: query description: Page number for pagination @@ -1256,7 +1789,7 @@ paths: type: integer minimum: 1 default: 1 - - name: get + - name: perPage in: query description: Number of items per page schema: @@ -1264,12 +1797,18 @@ paths: minimum: 1 default: 10 responses: - '200': + "200": description: List of general discussions content: application/json: schema: - $ref: '#/components/schemas/DiskusiUmumListResponse' + $ref: "#/components/schemas/DiskusiUmumListResponse" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error /discussion-general/{id}: get: @@ -1292,20 +1831,29 @@ paths: type: string - name: cat in: query - description: Category of data to retrieve + description: Category of data to retrieve (detail, member, or comment) schema: type: string enum: [detail, member, comment] + nullable: true responses: - '200': - description: Discussion details + "200": + description: Discussion details, members, or comments content: application/json: schema: oneOf: - - $ref: '#/components/schemas/DiskusiUmumDetailResponse' - - $ref: '#/components/schemas/DiskusiUmumMemberResponse' - - $ref: '#/components/schemas/DiskusiUmumCommentResponse' + - $ref: "#/components/schemas/DiskusiUmumDetailResponse" + - $ref: "#/components/schemas/DiskusiUmumMemberResponse" + - $ref: "#/components/schemas/DiskusiUmumCommentResponse" + "400": + description: Bad request (e.g., invalid parameters) + "404": + description: Discussion not found + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error # Division /division: @@ -1313,7 +1861,7 @@ paths: tags: - Division summary: Get divisions - description: Retrieves a paginated list of divisions filtered by village and group + description: Retrieves a paginated list of divisions filtered by village, group, and active status parameters: - name: desa in: query @@ -1321,21 +1869,24 @@ paths: description: Village ID schema: type: string - - name: active - in: query - description: Filter by active status - schema: - type: boolean - name: group in: query description: Filter by group ID schema: type: string + nullable: true - name: search in: query description: Search term for division name or description schema: type: string + nullable: true + - name: active + in: query + description: Filter by active status + schema: + type: boolean + nullable: true - name: page in: query description: Page number for pagination @@ -1343,7 +1894,7 @@ paths: type: integer minimum: 1 default: 1 - - name: get + - name: perPage in: query description: Number of items per page schema: @@ -1351,12 +1902,18 @@ paths: minimum: 1 default: 10 responses: - '200': + "200": description: List of divisions content: application/json: schema: - $ref: '#/components/schemas/DivisiListResponse' + $ref: "#/components/schemas/DivisiListResponse" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error /division/{id}: get: @@ -1378,12 +1935,83 @@ paths: schema: type: string responses: - '200': + "200": description: Division details content: application/json: schema: - $ref: '#/components/schemas/DivisiDetailResponse' + $ref: "#/components/schemas/DivisiDetailResponse" + "404": + description: Division not found + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error + + /division/report: + get: + tags: + - Division + summary: Get division reports + description: Retrieves reports for a specific division based on category (dokumen, event, or progress) + parameters: + - name: desa + in: query + required: true + description: Village ID + schema: + type: string + - name: cat + in: query + required: true + description: Category of report to retrieve (dokumen, event, or progress) + schema: + type: string + enum: [dokumen, event, progress] + - name: group + in: query + description: Group ID + schema: + type: string + nullable: true + - name: division + in: query + description: Division ID + schema: + type: string + nullable: true + - name: date-start + in: query + required: true + description: Start date for filtering + schema: + type: string + format: date + - name: date-end + in: query + required: true + description: End date for filtering + schema: + type: string + format: date + responses: + "200": + description: Division report data + content: + application/json: + schema: + oneOf: + - $ref: "#/components/schemas/DivisiReportDokumenResponse" + - $ref: "#/components/schemas/DivisiReportEventResponse" + - $ref: "#/components/schemas/DivisiReportProgressResponse" + "400": + description: Bad request (e.g., invalid parameters) + "404": + description: Division not found + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error # Document /document: @@ -1391,7 +2019,7 @@ paths: tags: - Document summary: Get documents - description: Retrieves a paginated list of documents and folders filtered by village and division + description: Retrieves a paginated list of documents and folders filtered by village, division, path, and active status parameters: - name: desa in: query @@ -1404,21 +2032,25 @@ paths: description: Filter by division ID schema: type: string + nullable: true - name: path in: query description: Filter by document path schema: type: string + nullable: true - name: active in: query description: Filter by active status schema: type: boolean + nullable: true - name: search in: query description: Search term for document name schema: type: string + nullable: true - name: page in: query description: Page number for pagination @@ -1426,7 +2058,7 @@ paths: type: integer minimum: 1 default: 1 - - name: get + - name: perPage in: query description: Number of items per page schema: @@ -1434,12 +2066,18 @@ paths: minimum: 1 default: 10 responses: - '200': + "200": description: List of documents and folders content: application/json: schema: - $ref: '#/components/schemas/DocumentListResponse' + $ref: "#/components/schemas/DocumentListResponse" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error # Group /group: @@ -1447,7 +2085,7 @@ paths: tags: - Group summary: Get groups - description: Retrieves a paginated list of groups filtered by village + description: Retrieves a paginated list of groups filtered by village, search term, and active status parameters: - name: desa in: query @@ -1455,16 +2093,18 @@ paths: description: Village ID schema: type: string - - name: active - in: query - description: Filter by active status - schema: - type: boolean - name: search in: query description: Search term for group name schema: type: string + nullable: true + - name: active + in: query + description: Filter by active status + schema: + type: boolean + nullable: true - name: page in: query description: Page number for pagination @@ -1472,7 +2112,7 @@ paths: type: integer minimum: 1 default: 1 - - name: get + - name: perPage in: query description: Number of items per page schema: @@ -1480,12 +2120,18 @@ paths: minimum: 1 default: 10 responses: - '200': + "200": description: List of groups content: application/json: schema: - $ref: '#/components/schemas/GroupListResponse' + $ref: "#/components/schemas/GroupListResponse" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error # Position /position: @@ -1493,7 +2139,7 @@ paths: tags: - Position summary: Get positions - description: Retrieves a paginated list of positions filtered by village and group + description: Retrieves a paginated list of positions filtered by village, group, search term, and active status parameters: - name: desa in: query @@ -1506,16 +2152,19 @@ paths: description: Filter by group ID schema: type: string + nullable: true - name: search in: query description: Search term for position name schema: type: string + nullable: true - name: active in: query description: Filter by active status schema: type: boolean + nullable: true - name: page in: query description: Page number for pagination @@ -1523,7 +2172,7 @@ paths: type: integer minimum: 1 default: 1 - - name: get + - name: perPage in: query description: Number of items per page schema: @@ -1531,12 +2180,18 @@ paths: minimum: 1 default: 10 responses: - '200': + "200": description: List of positions content: application/json: schema: - $ref: '#/components/schemas/PositionListResponse' + $ref: "#/components/schemas/PositionListResponse" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error # Project /project: @@ -1544,7 +2199,7 @@ paths: tags: - Project summary: Get projects - description: Retrieves a paginated list of projects filtered by village, group, and status + description: Retrieves a paginated list of projects filtered by village, group, status, and search term parameters: - name: desa in: query @@ -1552,22 +2207,25 @@ paths: description: Village ID schema: type: string + - name: group + in: query + description: Filter by group ID + schema: + type: string + nullable: true - name: status in: query description: Filter by project status schema: type: string enum: [segera, dikerjakan, selesai, batal] - - name: group - in: query - description: Filter by group ID - schema: - type: string + nullable: true - name: search in: query description: Search term for project title or description schema: type: string + nullable: true - name: page in: query description: Page number for pagination @@ -1575,7 +2233,7 @@ paths: type: integer minimum: 1 default: 1 - - name: get + - name: perPage in: query description: Number of items per page schema: @@ -1583,19 +2241,25 @@ paths: minimum: 1 default: 10 responses: - '200': + "200": description: List of projects content: application/json: schema: - $ref: '#/components/schemas/BaseResponseProjectList' + $ref: "#/components/schemas/BaseResponseProjectList" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error /project/{id}: get: tags: - Project summary: Get project details - description: Retrieves details of a specific project based on category + description: Retrieves details of a specific project based on category (data, task, file, member, or link) parameters: - name: id in: path @@ -1611,17 +2275,25 @@ paths: type: string enum: [data, task, file, member, link] responses: - '200': + "200": description: Project details content: application/json: schema: oneOf: - - $ref: '#/components/schemas/BaseResponseProjectDetail' - - $ref: '#/components/schemas/BaseResponseTaskList' - - $ref: '#/components/schemas/BaseResponseMemberList' - - $ref: '#/components/schemas/BaseResponseFileList' - - $ref: '#/components/schemas/BaseResponseLinkList' + - $ref: "#/components/schemas/BaseResponseProjectDetail" + - $ref: "#/components/schemas/BaseResponseTaskList" + - $ref: "#/components/schemas/BaseResponseFileList" + - $ref: "#/components/schemas/BaseResponseMemberList" + - $ref: "#/components/schemas/BaseResponseLinkList" + "400": + description: Bad request (e.g., invalid parameters) + "404": + description: Project not found + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error # Task /task: @@ -1629,7 +2301,7 @@ paths: tags: - Task summary: Get tasks - description: Retrieves a paginated list of tasks filtered by village, division, and status + description: Retrieves a paginated list of tasks filtered by village, division, status, and search term parameters: - name: desa in: query @@ -1642,17 +2314,20 @@ paths: description: Filter by division ID schema: type: string + nullable: true - name: status in: query description: Filter by task status schema: type: string enum: [segera, dikerjakan, selesai, batal] + nullable: true - name: search in: query description: Search term for task title or description schema: type: string + nullable: true - name: page in: query description: Page number for pagination @@ -1660,7 +2335,7 @@ paths: type: integer minimum: 1 default: 1 - - name: get + - name: perPage in: query description: Number of items per page schema: @@ -1668,19 +2343,25 @@ paths: minimum: 1 default: 10 responses: - '200': + "200": description: List of tasks content: application/json: schema: - $ref: '#/components/schemas/BaseResponseTaskList' + $ref: "#/components/schemas/BaseResponseTaskList" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error /task/{id}: get: tags: - Task summary: Get task details - description: Retrieves details of a specific task based on category + description: Retrieves details of a specific task based on category (data, task, file, member, or link) parameters: - name: id in: path @@ -1696,17 +2377,112 @@ paths: type: string enum: [data, task, file, member, link] responses: - '200': + "200": description: Task details content: application/json: schema: oneOf: - - $ref: '#/components/schemas/BaseResponseTaskDetail' - - $ref: '#/components/schemas/BaseResponseSubTaskList' - - $ref: '#/components/schemas/BaseResponseMemberList' - - $ref: '#/components/schemas/BaseResponseFileList' - - $ref: '#/components/schemas/BaseResponseLinkList' + - $ref: "#/components/schemas/BaseResponseTaskDetail" + - $ref: "#/components/schemas/BaseResponseSubTaskList" + - $ref: "#/components/schemas/BaseResponseFileList" + - $ref: "#/components/schemas/BaseResponseMemberList" + - $ref: "#/components/schemas/BaseResponseLinkList" + "400": + description: Bad request (e.g., invalid parameters) + "404": + description: Task not found + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error + + # User + /user: + get: + tags: + - User + summary: Get users + description: Retrieves a paginated list of users filtered by village, group, search term, and active status + parameters: + - name: desa + in: query + required: true + description: Village ID + schema: + type: string + - name: group + in: query + description: Filter by group ID + schema: + type: string + nullable: true + - name: search + in: query + description: Search term for user name + schema: + type: string + nullable: true + - name: active + in: query + description: Filter by active status + schema: + type: boolean + nullable: true + - name: page + in: query + description: Page number for pagination + schema: + type: integer + minimum: 1 + default: 1 + - name: perPage + in: query + description: Number of items per page + schema: + type: integer + minimum: 1 + default: 10 + responses: + "200": + description: List of users + content: + application/json: + schema: + $ref: "#/components/schemas/UserListResponse" + "400": + description: Bad request (e.g., invalid parameters) + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error + + /user/{id}: + get: + tags: + - User + summary: Get user details + description: Retrieves details of a specific user + parameters: + - name: id + in: path + required: true + description: User ID + schema: + type: string + responses: + "200": + description: User details + content: + application/json: + schema: + $ref: "#/components/schemas/UserDetailResponse" + "404": + description: User not found + "401": + description: Unauthorized (invalid or missing token) + "500": + description: Internal server error security: - bearerAuth: [] @@ -1719,13 +2495,13 @@ tags: - name: Calendar description: Operations related to calendar events - name: Discussion - description: Operations related to division discussions + description: Operations related to division-specific discussions - name: DiscussionGeneral description: Operations related to general discussions - name: Division - description: Operations related to division management + description: Operations related to division management and reports - name: Document - description: Operations related to document management + description: Operations related to document and folder management - name: Group description: Operations related to group management - name: Position @@ -1733,4 +2509,6 @@ tags: - name: Project description: Operations related to project management - name: Task - description: Operations related to task management \ No newline at end of file + description: Operations related to task management + - name: User + description: Operations related to user managementØ From ae3131ecf8bc9eab4f2f82c13e1c12f2cd73300b Mon Sep 17 00:00:00 2001 From: amal Date: Mon, 22 Sep 2025 15:43:11 +0800 Subject: [PATCH 20/21] upd : privacy --- public/util/privacy-policy.html | 253 ++++++++++++++++++++++++++++++++ 1 file changed, 253 insertions(+) create mode 100644 public/util/privacy-policy.html diff --git a/public/util/privacy-policy.html b/public/util/privacy-policy.html new file mode 100644 index 0000000..be39202 --- /dev/null +++ b/public/util/privacy-policy.html @@ -0,0 +1,253 @@ + + + + + + Privacy Policy — Bali Interaktif Perkasa + + + +
+
+

Privacy Policy

+
Last updated September 01, 2025
+
+
+ +
+

+ This Privacy Notice for Bali Interaktif Perkasa ("we," "us," or "our") describes how and why we might access, collect, store, use, and/or share ("process") your personal information when you use our services ("Services"), including when you: +

+
    +
  • Download and use our mobile application (Darmasaba mobile), or any other application of ours that links to this Privacy Notice.
  • +
  • Use Administration. This mobile application is specifically designed to help village officials manage data and monitor the progress of internal activities. It offers features such as data management by division, general activity monitoring, discussion forums, official announcements, and document folder management.
  • +
  • Engage with us in other related ways, including any sales, marketing, or events.
  • +
+

Questions or concerns? Reading this Privacy Notice will help you understand your privacy rights and choices. We are responsible for making decisions about how your personal information is processed. If you do not agree with our policies and practices, please do not use our Services. If you still have any questions or concerns, please contact us at bip.baliinteraktifperkasa@gmail.com.

+ +
+

Summary of Key Points

+

This summary provides key points from our Privacy Notice, but you can find out more details about any of these topics by using the table of contents below.

+
    +
  • What personal information do we process? We may process personal information depending on how you interact with us and the Services, the choices you make, and the products and features you use.
  • +
  • Do we process any sensitive personal information? We do not process sensitive personal information.
  • +
  • Do we collect any information from third parties? We do not collect any information from third parties.
  • +
  • How do we process your information? To provide, improve, and administer our Services; communicate with you; for security and fraud prevention; and to comply with law. We may also process your information for other purposes with your consent.
  • +
  • In what situations and with which parties do we share personal information? We may share information in specific situations and with specific third parties.
  • +
  • How do we keep your information safe? We have organizational and technical measures to protect your personal information; however, no method is 100% secure.
  • +
  • What are your rights? Depending on your location, you may have certain rights regarding your personal information.
  • +
  • How do you exercise your rights? The easiest way is by emailing bip.baliinteraktifperkasa@gmail.com.
  • +
+

Want to learn more about what we do with information we collect? Review the Privacy Notice in full below.

+
+ + + +
+

1. WHAT INFORMATION DO WE COLLECT?

+

Personal information you disclose to us

+

In Short: We collect personal information that you provide to us.

+

We collect personal information that you voluntarily provide to us when you express an interest in obtaining information about us or our products and Services, when you participate in activities on the Services, or otherwise when you contact us.

+

Personal Information Provided by You

+

The personal information we collect depends on the context of your interactions with us and the Services, the choices you make, and the products and features you use. This may include:

+
    +
  • names
  • +
  • phone numbers
  • +
  • email addresses
  • +
+

Sensitive Information

+

We do not process sensitive information.

+

Application Data

+

If you use our application(s), we may also collect the following information if you choose to provide us with access or permission:

+
    +
  • Mobile Device Access. We may request access or permission to certain features from your mobile device, including your mobile device's camera, and other features. You may change access or permissions in your device's settings.
  • +
  • Push Notifications. We may request to send you push notifications regarding your account or certain features of the application(s). You may opt out in your device's settings.
  • +
+

This information is primarily needed to maintain the security and operation of our application(s), for troubleshooting, and for internal analytics and reporting purposes.

+

All personal information that you provide to us must be true, complete, and accurate, and you must notify us of any changes to such personal information.

+
+ +
+

2. HOW DO WE PROCESS YOUR INFORMATION?

+

In Short: We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent.

+

We process your personal information for a variety of reasons, depending on how you interact with our Services, including:

+
    +
  • To deliver and facilitate delivery of services to the user. We may process your information to provide you with the requested service.
  • +
  • To enable user-to-user communications. We may process your information if you choose to use offerings that allow communication with another user.
  • +
  • To evaluate and improve our Services, products, marketing, and your experience. We may process your information to identify usage trends, determine the effectiveness of our promotional campaigns, and to evaluate and improve our Services, products, marketing, and your experience.
  • +
+
+ +
+

3. WHEN AND WITH WHOM DO WE SHARE YOUR PERSONAL INFORMATION?

+

In Short: We may share information in specific situations described in this section and/or with the following third parties.

+

Vendors, Consultants, and Other Third-Party Service Providers. We may share your data with third-party vendors, service providers, contractors, or agents ("third parties") who perform services for us or on our behalf and require access to such information to do that work.

+

The third parties we may share personal information with include:

+
    +
  • Functionality and Infrastructure Optimization: Firebase Realtime Database and Cloud Functions for Firebase
  • +
  • Functionality & Infrastructure Optimization: Expo / EAS Services
  • +
+

We may also need to share your personal information in the following situations:

+
    +
  • Business Transfers. We may share or transfer your information in connection with, or during negotiations of, any merger, sale of company assets, financing, or acquisition of all or a portion of our business to another company.
  • +
  • Other Users. When you share personal information (for example, by posting comments or other content to the Services) or interact with public areas of the Services, such information may be viewed by all users and may be publicly available outside the Services in perpetuity. Other users may view descriptions of your activity, communicate with you, and view your profile.
  • +
+
+ +
+

4. HOW LONG DO WE KEEP YOUR INFORMATION?

+

In Short: We keep your information for as long as necessary to fulfill the purposes outlined in this Privacy Notice unless otherwise required by law.

+

We will only keep your personal information as long as necessary for the purposes set out in this Privacy Notice, unless a longer retention period is required or permitted by law (such as tax, accounting, or other legal requirements).

+

When we have no ongoing legitimate business need to process your personal information, we will delete or anonymize such information. If deletion is not possible (for example, if your personal information is stored in backup archives), we will securely store your personal information and isolate it from any further processing until deletion is possible.

+
+ +
+

5. HOW DO WE KEEP YOUR INFORMATION SAFE?

+

In Short: We aim to protect your personal information through a system of organizational and technical security measures.

+

We have implemented appropriate and reasonable technical and organizational security measures designed to protect the security of any personal information we process. However, despite our safeguards and efforts to secure your information, no electronic transmission over the Internet or information storage technology can be guaranteed to be 100% secure. Transmission of personal information to and from our Services is at your own risk. You should only access the Services within a secure environment.

+
+ +
+

6. DO WE COLLECT INFORMATION FROM MINORS?

+

In Short: We do not knowingly collect data from or market to children under 18 years of age.

+

We do not knowingly collect, solicit data from, or market to children under 18 years of age, nor do we knowingly sell such personal information. By using the Services, you represent that you are at least 18 or that you are the parent or guardian of such a minor and consent to such minor dependent’s use of the Services. If we learn that personal information from users less than 18 years of age has been collected, we will deactivate the account and take reasonable measures to promptly delete such data from our records. If you become aware of any data we may have collected from children under age 18, please contact us at bip.baliinteraktifperkasa@gmail.com.

+
+ +
+

7. WHAT ARE YOUR PRIVACY RIGHTS?

+

In Short: You may review, change, or terminate your account at any time, depending on your country, province, or state of residence.

+

Withdrawing your consent: If we are relying on your consent to process your personal information (which may be express and/or implied consent depending on the applicable law), you have the right to withdraw your consent at any time. You can do so by contacting us using the details in the section "HOW CAN YOU CONTACT US ABOUT THIS NOTICE?" below. This will not affect the lawfulness of the processing before its withdrawal, nor will it affect processing conducted in reliance on lawful processing grounds other than consent where permitted by law.

+

If you have questions or comments about your privacy rights, email us at bip.baliinteraktifperkasa@gmail.com.

+
+ +
+

8. CONTROLS FOR DO-NOT-TRACK FEATURES

+

Most web browsers and some mobile operating systems and applications include a Do-Not-Track ("DNT") setting you can activate to signal your privacy preference not to have data about your online browsing activities monitored and collected. At this stage, no uniform technology standard for recognizing and implementing DNT signals has been finalized. As such, we do not currently respond to DNT browser signals or any other mechanism that automatically communicates your choice not to be tracked online. If a standard for online tracking is adopted that we must follow in the future, we will inform you about that practice in a revised version of this Privacy Notice.

+
+ +
+

9. DO WE MAKE UPDATES TO THIS NOTICE?

+

In Short: Yes, we will update this notice as necessary to stay compliant with relevant laws.

+

We may update this Privacy Notice from time to time. The updated version will be indicated by an updated "Revised" date at the top of this Privacy Notice. If we make material changes, we may notify you by prominently posting a notice of such changes or by directly sending you a notification. We encourage you to review this Privacy Notice frequently to stay informed of how we are protecting your information.

+
+ +
+

10. HOW CAN YOU CONTACT US ABOUT THIS NOTICE?

+
+Bali Interaktif Perkasa +Park23 Creative Hub, Bali Interaktif Perkasa - Private Office +Jl. Kediri 3rd Floor, Number 01 - 02, Tuban +Badung, Bali 80361 +Indonesia +
+

Email: bip.baliinteraktifperkasa@gmail.com

+
+ +
+

11. HOW CAN YOU REVIEW, UPDATE, OR DELETE THE DATA WE COLLECT FROM YOU?

+

You have the right to request access to the personal information we collect from you, details about how we have processed it, correct inaccuracies, or delete your personal information. You may also have the right to withdraw your consent to our processing of your personal information. These rights may be limited in some circumstances by applicable law.

+

To make a request, please contact us at bip.baliinteraktifperkasa@gmail.com.

+
+ +
+

© Bali Interaktif Perkasa. All rights reserved.

+
+
+ + + + From 4064497794397537b421d612121c9c289d5573df Mon Sep 17 00:00:00 2001 From: amal Date: Mon, 22 Sep 2025 15:45:17 +0800 Subject: [PATCH 21/21] upd: api version --- src/app/api/version-app/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/api/version-app/route.ts b/src/app/api/version-app/route.ts index 19f756a..1d58873 100644 --- a/src/app/api/version-app/route.ts +++ b/src/app/api/version-app/route.ts @@ -2,7 +2,7 @@ import { NextResponse } from "next/server"; export async function GET(request: Request) { try { - return NextResponse.json({ success: true, version: "1.9.0", tahap: "beta", update: "-api mobile; -login tanpa otp (mobile app); -tambah laporan pada project dan tugas divisi; -tambah upload link pada project dan tugas divisi; -tambah detail tanggal dan jam pada project dan tugas divisi; -api jenna ai" }, { status: 200 }); + return NextResponse.json({ success: true, version: "2.0.0", tahap: "beta", update: "-api mobile; -login tanpa otp (mobile app); -tambah laporan pada project dan tugas divisi; -tambah upload link pada project dan tugas divisi; -tambah detail tanggal dan jam pada project dan tugas divisi; -api jenna ai; -privacy policy" }, { status: 200 }); } catch (error) { console.error(error); return NextResponse.json({ success: false, version: "Gagal mendapatkan version, coba lagi nanti (error: 500)", reason: (error as Error).message, }, { status: 500 });