Merge pull request 'amalia/22-mei-26' (#52) from amalia/22-mei-26 into join
Reviewed-on: #52
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sistem-desa-mandiri",
|
||||
"version": "0.1.16",
|
||||
"version": "0.1.17",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --experimental-https",
|
||||
|
||||
1
prisma/migrations/20260522064632_auto/migration.sql
Normal file
1
prisma/migrations/20260522064632_auto/migration.sql
Normal file
@@ -0,0 +1 @@
|
||||
-- This is an empty migration.
|
||||
@@ -117,50 +117,32 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
|
||||
)
|
||||
.get("/daily-activity", async ({ query, set }) => {
|
||||
try {
|
||||
// const data = await prisma.userLog.findMany({
|
||||
// where: {
|
||||
// User: {
|
||||
// Village: {
|
||||
// isDummy: false
|
||||
// }
|
||||
// },
|
||||
// createdAt: {
|
||||
// gte: moment().subtract(7, 'days').toDate(),
|
||||
// lte: moment().toDate(),
|
||||
// }
|
||||
// },
|
||||
// select: {
|
||||
// createdAt: true,
|
||||
// }
|
||||
// })
|
||||
const VALID_RANGES = [7, 30, 90];
|
||||
const range = VALID_RANGES.includes(Number(query.range)) ? Number(query.range) : 7;
|
||||
|
||||
const data = await prisma.$queryRaw`
|
||||
SELECT
|
||||
SELECT
|
||||
DATE(ul."createdAt") AS tanggal,
|
||||
COUNT(*) AS total
|
||||
FROM "UserLog" ul
|
||||
JOIN "User" u ON ul."idUser" = u."id"
|
||||
JOIN "Village" v ON u."idVillage" = v."id"
|
||||
WHERE v."isDummy" = false
|
||||
AND ul."createdAt" >= NOW() - INTERVAL '7 days'
|
||||
AND ul."createdAt" >= NOW() - (${range} * INTERVAL '1 day')
|
||||
GROUP BY tanggal
|
||||
ORDER BY tanggal;` as any[];
|
||||
|
||||
const result = [];
|
||||
|
||||
// ubah data ke map biar gampang lookup
|
||||
const map = data.reduce((acc: any, item: any) => {
|
||||
const key = moment(item.tanggal).format('YYYY-MM-DD');
|
||||
acc[key] = Number(item.total);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// generate 7 hari terakhir
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
for (let i = range - 1; i >= 0; i--) {
|
||||
const date = moment().subtract(i, 'days');
|
||||
|
||||
const key = date.format('YYYY-MM-DD');
|
||||
|
||||
result.push({
|
||||
date: date.format('DD MMM'),
|
||||
logs: map[key] || 0
|
||||
@@ -183,45 +165,39 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
|
||||
}
|
||||
},
|
||||
{
|
||||
query: t.Object({
|
||||
range: t.Optional(t.String({ description: "Rentang hari: 7, 30, atau 90 (default: 7)" })),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Daily Activity",
|
||||
description: "Menu Overview - Mendapatkan data grafik aktivitas harian semua desa.",
|
||||
description: "Menu Overview - Mendapatkan data grafik aktivitas harian semua desa. Gunakan ?range=30 atau ?range=90 untuk rentang lebih panjang.",
|
||||
tags: ["overview"],
|
||||
},
|
||||
}
|
||||
)
|
||||
.get("/comparison-activity", async ({ query, set }) => {
|
||||
try {
|
||||
const villages = await prisma.village.findMany({
|
||||
where: { isDummy: false },
|
||||
select: { name: true },
|
||||
});
|
||||
const VALID_RANGES = [7, 30, 90];
|
||||
const range = VALID_RANGES.includes(Number(query.range)) ? Number(query.range) : 7;
|
||||
|
||||
const data = await prisma.$queryRaw`
|
||||
SELECT
|
||||
SELECT
|
||||
v."name",
|
||||
COUNT(ul."id") AS total_logs
|
||||
FROM "UserLog" ul
|
||||
JOIN "User" u ON ul."idUser" = u."id"
|
||||
JOIN "Village" v ON u."idVillage" = v."id"
|
||||
WHERE v."isDummy" = false
|
||||
AND ul."createdAt" >= NOW() - INTERVAL '7 days'
|
||||
AND ul."createdAt" >= NOW() - (${range} * INTERVAL '1 day')
|
||||
GROUP BY v."id", v."name"
|
||||
ORDER BY total_logs DESC;
|
||||
` as any[];
|
||||
|
||||
const logMap: Record<string, number> = {};
|
||||
|
||||
data.forEach((item) => {
|
||||
logMap[item.name] = Number(item.total_logs);
|
||||
});
|
||||
|
||||
const result = villages.map((v) => ({
|
||||
village: v.name,
|
||||
activity: logMap[v.name] || 0,
|
||||
const result = data.map((item: any) => ({
|
||||
village: item.name,
|
||||
activity: Number(item.total_logs),
|
||||
}));
|
||||
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: "Berhasil mendapatkan data",
|
||||
@@ -238,9 +214,12 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
|
||||
}
|
||||
},
|
||||
{
|
||||
query: t.Object({
|
||||
range: t.Optional(t.String({ description: "Rentang hari: 7, 30, atau 90 (default: 7)" })),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Comparison Activity",
|
||||
description: "Menu Overview - Mendapatkan data grafik perbandingan aktivitas desa selama 7 hari terakhir.",
|
||||
description: "Menu Overview - Mendapatkan data grafik perbandingan aktivitas desa. Gunakan ?range=30 atau ?range=90 untuk rentang lebih panjang.",
|
||||
tags: ["overview"],
|
||||
},
|
||||
}
|
||||
@@ -1061,37 +1040,31 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
|
||||
}
|
||||
)
|
||||
.get("/log-all-villages", async ({ query, set }) => {
|
||||
const { page = 1, search } = query;
|
||||
const { page = 1, search, action, idVillage, dateFrom, dateTo } = query;
|
||||
const pageNum = Number(page) || 1;
|
||||
const take = 15;
|
||||
const skip = (pageNum - 1) * take;
|
||||
|
||||
const whereClause = {
|
||||
...(action && { action: action.toUpperCase() }),
|
||||
...(idVillage && { User: { idVillage } }),
|
||||
...(dateFrom || dateTo) && {
|
||||
createdAt: {
|
||||
...(dateFrom && { gte: new Date(dateFrom) }),
|
||||
...(dateTo && { lte: new Date(new Date(dateTo).setHours(23, 59, 59, 999)) }),
|
||||
},
|
||||
},
|
||||
...(search && {
|
||||
OR: [
|
||||
{ User: { name: { contains: search, mode: "insensitive" as const } } },
|
||||
{ User: { Village: { name: { contains: search, mode: "insensitive" as const } } } },
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
try {
|
||||
const dataLog = await prisma.userLog.findMany({
|
||||
where: {
|
||||
...(search && {
|
||||
OR: [
|
||||
{
|
||||
User: {
|
||||
name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
User: {
|
||||
Village: {
|
||||
name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
where: whereClause,
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
@@ -1115,32 +1088,7 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
|
||||
take,
|
||||
});
|
||||
|
||||
const total = await prisma.userLog.count({
|
||||
where: {
|
||||
...(search && {
|
||||
OR: [
|
||||
{
|
||||
User: {
|
||||
name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
User: {
|
||||
Village: {
|
||||
name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
});
|
||||
const total = await prisma.userLog.count({ where: whereClause });
|
||||
|
||||
const result = dataLog.map((item) => ({
|
||||
id: item.id,
|
||||
@@ -1175,65 +1123,49 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
|
||||
{
|
||||
query: t.Object({
|
||||
page: t.Optional(t.String({ description: "Halaman" })),
|
||||
search: t.Optional(t.String({ description: "Pencarian" })),
|
||||
search: t.Optional(t.String({ description: "Pencarian nama user atau desa" })),
|
||||
action: t.Optional(t.String({ description: "Filter jenis aksi: LOGIN | LOGOUT | CREATE | UPDATE | DELETE" })),
|
||||
idVillage: t.Optional(t.String({ description: "Filter berdasarkan ID desa" })),
|
||||
dateFrom: t.Optional(t.String({ description: "Tanggal mulai (ISO 8601, e.g. 2026-05-01)" })),
|
||||
dateTo: t.Optional(t.String({ description: "Tanggal akhir (ISO 8601, e.g. 2026-05-31)" })),
|
||||
}),
|
||||
detail: {
|
||||
summary: "Log Villages",
|
||||
description:
|
||||
"Mendapatkan data log aktivitas desa berdasarkan halaman dan pencarian",
|
||||
description: "Mendapatkan data log aktivitas desa dengan filter aksi, desa, rentang tanggal, pencarian, dan paginasi",
|
||||
tags: ["log-activity"],
|
||||
},
|
||||
}
|
||||
)
|
||||
.get("/user", async ({ query, set }) => {
|
||||
const { page = 1, search } = query;
|
||||
const { page = 1, search, isActive, idUserRole, idVillage, orderBy = 'createdAt', orderDir = 'desc' } = query;
|
||||
const pageNum = Number(page) || 1;
|
||||
const take = 15;
|
||||
const skip = (pageNum - 1) * take;
|
||||
|
||||
const SORTABLE_FIELDS = ['name', 'email', 'isActive', 'idUserRole', 'createdAt'] as const;
|
||||
type SortableField = typeof SORTABLE_FIELDS[number];
|
||||
const safeOrderBy: SortableField = SORTABLE_FIELDS.includes(orderBy as SortableField) ? (orderBy as SortableField) : 'createdAt';
|
||||
const safeOrderDir = orderDir === 'asc' ? 'asc' : 'desc';
|
||||
|
||||
const whereClause = {
|
||||
...(isActive !== undefined && { isActive: isActive === 'true' }),
|
||||
...(idUserRole && { idUserRole }),
|
||||
...(idVillage && { idVillage }),
|
||||
...(search && {
|
||||
OR: [
|
||||
{ name: { contains: search, mode: "insensitive" as const } },
|
||||
{ phone: { contains: search, mode: "insensitive" as const } },
|
||||
{ email: { contains: search, mode: "insensitive" as const } },
|
||||
{ nik: { contains: search, mode: "insensitive" as const } },
|
||||
{ Village: { name: { contains: search, mode: "insensitive" as const } } },
|
||||
{ idUserRole: search },
|
||||
],
|
||||
}),
|
||||
};
|
||||
|
||||
try {
|
||||
const data = await prisma.user.findMany({
|
||||
where: {
|
||||
...(search && {
|
||||
OR: [
|
||||
{
|
||||
name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
phone: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
email: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
nik: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
Village: {
|
||||
name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
idUserRole: search,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
where: whereClause,
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
@@ -1270,55 +1202,13 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
|
||||
},
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
[safeOrderBy]: safeOrderDir,
|
||||
},
|
||||
skip,
|
||||
take,
|
||||
});
|
||||
|
||||
const total = await prisma.user.count({
|
||||
where: {
|
||||
...(search && {
|
||||
OR: [
|
||||
{
|
||||
name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
phone: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
email: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
nik: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
{
|
||||
Village: {
|
||||
name: {
|
||||
contains: search,
|
||||
mode: "insensitive",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
idUserRole: search,
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
});
|
||||
const total = await prisma.user.count({ where: whereClause });
|
||||
|
||||
const result = data.map((item) => ({
|
||||
id: item.id,
|
||||
@@ -1363,12 +1253,16 @@ const MonitoringServer = new Elysia({ prefix: "/api/monitoring" })
|
||||
{
|
||||
query: t.Object({
|
||||
page: t.Optional(t.String({ description: "Halaman" })),
|
||||
search: t.Optional(t.String({ description: "Pencarian" })),
|
||||
search: t.Optional(t.String({ description: "Pencarian nama/NIK/email/telepon" })),
|
||||
isActive: t.Optional(t.String({ description: "Filter status: 'true' atau 'false'" })),
|
||||
idUserRole: t.Optional(t.String({ description: "Filter berdasarkan ID role" })),
|
||||
idVillage: t.Optional(t.String({ description: "Filter berdasarkan ID desa" })),
|
||||
orderBy: t.Optional(t.String({ description: "Kolom urutan: name | email | isActive | idUserRole | createdAt (default: createdAt)" })),
|
||||
orderDir: t.Optional(t.String({ description: "Arah urutan: asc | desc (default: desc)" })),
|
||||
}),
|
||||
detail: {
|
||||
summary: "User",
|
||||
description:
|
||||
"Mendapatkan data user berdasarkan halaman dan pencarian",
|
||||
description: "Mendapatkan data user dengan filter status, role, desa, pencarian, dan pengurutan",
|
||||
tags: ["user"],
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user