upd: menerapkan log pada semua aksi

This commit is contained in:
2026-04-13 16:42:36 +08:00
parent 14a9e2c687
commit 2cf96135f9
8 changed files with 121 additions and 31 deletions

View File

@@ -3,6 +3,7 @@ import { html } from '@elysiajs/html'
import { Elysia } from 'elysia' import { Elysia } from 'elysia'
import { prisma } from './lib/db' import { prisma } from './lib/db'
import { env } from './lib/env' import { env } from './lib/env'
import { createSystemLog } from './lib/logger'
export function createApp() { export function createApp() {
return new Elysia() return new Elysia()
@@ -43,13 +44,20 @@ export function createApp() {
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours
await prisma.session.create({ data: { token, userId: user.id, expiresAt } }) await prisma.session.create({ data: { token, userId: user.id, expiresAt } })
set.headers['set-cookie'] = `session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400` set.headers['set-cookie'] = `session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400`
await createSystemLog(user.id, 'LOGIN', 'Logged in successfully')
return { user: { id: user.id, name: user.name, email: user.email, role: user.role } } return { user: { id: user.id, name: user.name, email: user.email, role: user.role } }
}) })
.post('/api/auth/logout', async ({ request, set }) => { .post('/api/auth/logout', async ({ request, set }) => {
const cookie = request.headers.get('cookie') ?? '' const cookie = request.headers.get('cookie') ?? ''
const token = cookie.match(/session=([^;]+)/)?.[1] const token = cookie.match(/session=([^;]+)/)?.[1]
if (token) await prisma.session.deleteMany({ where: { token } }) if (token) {
const sessionObj = await prisma.session.findUnique({ where: { token } })
if (sessionObj) {
await createSystemLog(sessionObj.userId, 'LOGOUT', 'Logged out successfully')
await prisma.session.deleteMany({ where: { token } })
}
}
set.headers['set-cookie'] = 'session=; Path=/; HttpOnly; Max-Age=0' set.headers['set-cookie'] = 'session=; Path=/; HttpOnly; Max-Age=0'
return { ok: true } return { ok: true }
}) })
@@ -139,6 +147,8 @@ export function createApp() {
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000)
await prisma.session.create({ data: { token, userId: user.id, expiresAt } }) await prisma.session.create({ data: { token, userId: user.id, expiresAt } })
await createSystemLog(user.id, 'LOGIN', 'Logged in via Google')
set.headers['set-cookie'] = `session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400` set.headers['set-cookie'] = `session=${token}; Path=/; HttpOnly; SameSite=Lax; Max-Age=86400`
set.status = 302; set.headers['location'] = user.role === 'SUPER_ADMIN' ? '/dashboard' : '/profile' set.status = 302; set.headers['location'] = user.role === 'SUPER_ADMIN' ? '/dashboard' : '/profile'
}) })
@@ -203,6 +213,25 @@ export function createApp() {
} }
}) })
.post('/api/logs', async ({ request, set }) => {
const cookie = request.headers.get('cookie') ?? ''
const token = cookie.match(/session=([^;]+)/)?.[1]
let userId: string | undefined
if (token) {
const session = await prisma.session.findUnique({ where: { token } })
if (session && session.expiresAt > new Date()) {
userId = session.userId
}
}
const body = (await request.json()) as { type: string, message: string }
const actingUserId = userId || (await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } }))?.id || ''
await createSystemLog(actingUserId, body.type as any, body.message)
return { ok: true }
})
.get('/api/operators', async ({ query }) => { .get('/api/operators', async ({ query }) => {
const page = Number(query.page) || 1 const page = Number(query.page) || 1
const limit = Number(query.limit) || 20 const limit = Number(query.limit) || 20
@@ -321,6 +350,9 @@ export function createApp() {
} }
const body = (await request.json()) as any const body = (await request.json()) as any
const defaultAdmin = await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } })
const actingUserId = userId || defaultAdmin?.id || ''
const bug = await prisma.bug.create({ const bug = await prisma.bug.create({
data: { data: {
app: body.app, app: body.app,
@@ -339,7 +371,7 @@ export function createApp() {
} : undefined, } : undefined,
logs: { logs: {
create: { create: {
userId: userId || (await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } }))?.id || '', userId: actingUserId,
status: body.status || 'OPEN', status: body.status || 'OPEN',
description: 'Bug reported initially.', description: 'Bug reported initially.',
}, },

View File

@@ -34,4 +34,5 @@ export const API_URLS = {
getBugs: (page: number, search: string, app: string, status: string) => getBugs: (page: number, search: string, app: string, status: string) =>
`/api/bugs?page=${page}&search=${encodeURIComponent(search)}&app=${app}&status=${status}`, `/api/bugs?page=${page}&search=${encodeURIComponent(search)}&app=${app}&status=${status}`,
createBug: () => `/api/bugs`, createBug: () => `/api/bugs`,
createLog: () => `/api/logs`,
} }

View File

@@ -1,33 +1,30 @@
import { useEffect, useState } from 'react'
import { notifications } from '@mantine/notifications'
import { VillageActivityLineChart, VillageComparisonBarChart } from '@/frontend/components/DashboardCharts' import { VillageActivityLineChart, VillageComparisonBarChart } from '@/frontend/components/DashboardCharts'
import { ErrorDataTable } from '@/frontend/components/ErrorDataTable' import { ErrorDataTable } from '@/frontend/components/ErrorDataTable'
import { SummaryCard } from '@/frontend/components/SummaryCard' import { SummaryCard } from '@/frontend/components/SummaryCard'
import { import {
ActionIcon, Badge,
Button,
Group, Group,
Modal,
SimpleGrid, SimpleGrid,
Stack, Stack,
Text,
Title,
Modal,
Button,
TextInput,
Switch, Switch,
Badge, Text,
Textarea, Textarea,
Skeleton TextInput,
Title
} from '@mantine/core' } from '@mantine/core'
import { useDisclosure } from '@mantine/hooks' import { useDisclosure } from '@mantine/hooks'
import { createFileRoute, useParams, useNavigate } from '@tanstack/react-router' import { notifications } from '@mantine/notifications'
import useSWR from 'swr' import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
import { useEffect, useState } from 'react'
import { import {
TbActivity, TbActivity,
TbAlertTriangle, TbAlertTriangle,
TbBuildingCommunity, TbBuildingCommunity,
TbRefresh,
TbVersions TbVersions
} from 'react-icons/tb' } from 'react-icons/tb'
import useSWR from 'swr'
import { API_URLS } from '../config/api' import { API_URLS } from '../config/api'
export const Route = createFileRoute('/apps/$appId/')({ export const Route = createFileRoute('/apps/$appId/')({
@@ -89,6 +86,12 @@ function AppOverviewPage() {
}) })
if (response.ok) { if (response.ok) {
await fetch(API_URLS.createLog(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'UPDATE', message: `Update version information: ${JSON.stringify({ latestVersion, minVersion, maintenance, messageUpdate })}` })
}).catch(console.error)
notifications.show({ notifications.show({
title: 'Update Successful', title: 'Update Successful',
message: 'Application version information has been updated.', message: 'Application version information has been updated.',

View File

@@ -169,6 +169,12 @@ function UsersIndexPage() {
const result = await res.json() const result = await res.json()
if (result.success) { if (result.success) {
await fetch(API_URLS.createLog(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'CREATE', message: `Didaftarkan user (${appId}) baru: ${form.name}-${form.nik}` })
}).catch(console.error)
notifications.show({ notifications.show({
title: 'Success', title: 'Success',
message: 'User has been created successfully.', message: 'User has been created successfully.',
@@ -252,6 +258,12 @@ function UsersIndexPage() {
const result = await res.json() const result = await res.json()
if (result.success) { if (result.success) {
await fetch(API_URLS.createLog(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'UPDATE', message: `Data user (${appId}) diperbarui: ${editForm.name}-${editForm.id}` })
}).catch(console.error)
notifications.show({ notifications.show({
title: 'Success', title: 'Success',
message: 'User has been updated successfully.', message: 'User has been updated successfully.',

View File

@@ -196,6 +196,12 @@ function VillageDetailPage() {
}) })
if (res.ok) { if (res.ok) {
await fetch(API_URLS.createLog(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'UPDATE', message: `Data desa (${appId}) diperbarui: ${editForm.name}-${village.id}` })
}).catch(console.error)
notifications.show({ notifications.show({
title: 'Success', title: 'Success',
message: 'Village data has been updated successfully.', message: 'Village data has been updated successfully.',
@@ -238,6 +244,12 @@ function VillageDetailPage() {
}) })
if (res.ok) { if (res.ok) {
await fetch(API_URLS.createLog(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'UPDATE', message: `Status desa (${appId}) diperbarui (${!village.isActive ? 'activated' : 'deactivated'}): ${village.name}-${village.id}` })
}).catch(console.error)
notifications.show({ notifications.show({
title: 'Success', title: 'Success',
message: `Village status has been ${!village.isActive ? 'activated' : 'deactivated'}.`, message: `Village status has been ${!village.isActive ? 'activated' : 'deactivated'}.`,

View File

@@ -283,6 +283,12 @@ function AppVillagesIndexPage() {
}) })
if (res.ok) { if (res.ok) {
await fetch(API_URLS.createLog(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'CREATE', message: `Desa baru didaftarkan: ${form.name}` })
}).catch(console.error)
notifications.show({ notifications.show({
title: 'Success', title: 'Success',
message: 'Village has been successfully registered.', message: 'Village has been successfully registered.',

View File

@@ -92,6 +92,12 @@ function ListErrorsPage() {
}) })
if (res.ok) { if (res.ok) {
await fetch(API_URLS.createLog(), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ type: 'CREATE', message: `Report bug baru ditambahkan: ${createForm.description.substring(0, 50)}${createForm.description.length > 50 ? '...' : ''}` })
}).catch(console.error)
notifications.show({ notifications.show({
title: 'Success', title: 'Success',
message: 'Bug report has been created.', message: 'Bug report has been created.',

18
src/lib/logger.ts Normal file
View File

@@ -0,0 +1,18 @@
import { prisma } from './db'
import { LogType } from '../../generated/prisma'
export async function createSystemLog(userId: string, type: LogType, message: string) {
try {
return await prisma.log.create({
data: {
userId,
type,
message,
},
})
} catch (error) {
console.error('[Logger Error]', error)
// Don't throw, we don't want logging errors to break the main application flow
return null
}
}