diff --git a/src/app.ts b/src/app.ts
index 4e236e6..f4f93e4 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -3,6 +3,7 @@ import { html } from '@elysiajs/html'
import { Elysia } from 'elysia'
import { prisma } from './lib/db'
import { env } from './lib/env'
+import { createSystemLog } from './lib/logger'
export function createApp() {
return new Elysia()
@@ -43,13 +44,20 @@ export function createApp() {
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000) // 24 hours
await prisma.session.create({ data: { token, userId: user.id, expiresAt } })
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 } }
})
.post('/api/auth/logout', async ({ request, set }) => {
const cookie = request.headers.get('cookie') ?? ''
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'
return { ok: true }
})
@@ -139,6 +147,8 @@ export function createApp() {
const expiresAt = new Date(Date.now() + 24 * 60 * 60 * 1000)
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.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 }) => {
const page = Number(query.page) || 1
const limit = Number(query.limit) || 20
@@ -321,6 +350,9 @@ export function createApp() {
}
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({
data: {
app: body.app,
@@ -339,7 +371,7 @@ export function createApp() {
} : undefined,
logs: {
create: {
- userId: userId || (await prisma.user.findFirst({ where: { role: 'SUPER_ADMIN' } }))?.id || '',
+ userId: actingUserId,
status: body.status || 'OPEN',
description: 'Bug reported initially.',
},
diff --git a/src/frontend/config/api.ts b/src/frontend/config/api.ts
index 8c8a833..9472c17 100644
--- a/src/frontend/config/api.ts
+++ b/src/frontend/config/api.ts
@@ -34,4 +34,5 @@ export const API_URLS = {
getBugs: (page: number, search: string, app: string, status: string) =>
`/api/bugs?page=${page}&search=${encodeURIComponent(search)}&app=${app}&status=${status}`,
createBug: () => `/api/bugs`,
+ createLog: () => `/api/logs`,
}
diff --git a/src/frontend/routes/apps.$appId.index.tsx b/src/frontend/routes/apps.$appId.index.tsx
index acb394f..89f6603 100644
--- a/src/frontend/routes/apps.$appId.index.tsx
+++ b/src/frontend/routes/apps.$appId.index.tsx
@@ -1,33 +1,30 @@
-import { useEffect, useState } from 'react'
-import { notifications } from '@mantine/notifications'
import { VillageActivityLineChart, VillageComparisonBarChart } from '@/frontend/components/DashboardCharts'
import { ErrorDataTable } from '@/frontend/components/ErrorDataTable'
import { SummaryCard } from '@/frontend/components/SummaryCard'
import {
- ActionIcon,
+ Badge,
+ Button,
Group,
+ Modal,
SimpleGrid,
Stack,
- Text,
- Title,
- Modal,
- Button,
- TextInput,
Switch,
- Badge,
+ Text,
Textarea,
- Skeleton
+ TextInput,
+ Title
} from '@mantine/core'
import { useDisclosure } from '@mantine/hooks'
-import { createFileRoute, useParams, useNavigate } from '@tanstack/react-router'
-import useSWR from 'swr'
+import { notifications } from '@mantine/notifications'
+import { createFileRoute, useNavigate, useParams } from '@tanstack/react-router'
+import { useEffect, useState } from 'react'
import {
TbActivity,
TbAlertTriangle,
TbBuildingCommunity,
- TbRefresh,
TbVersions
} from 'react-icons/tb'
+import useSWR from 'swr'
import { API_URLS } from '../config/api'
export const Route = createFileRoute('/apps/$appId/')({
@@ -89,6 +86,12 @@ function AppOverviewPage() {
})
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({
title: 'Update Successful',
message: 'Application version information has been updated.',
@@ -118,29 +121,29 @@ function AppOverviewPage() {
<>
- setLatestVersion(e.currentTarget.value)}
/>
- setMinVersion(e.currentTarget.value)}
/>
-
-
+
{/* ── Edit Village Modal ── */}
50 ? '...' : ''}` })
+ }).catch(console.error)
+
notifications.show({
title: 'Success',
message: 'Bug report has been created.',
diff --git a/src/lib/logger.ts b/src/lib/logger.ts
new file mode 100644
index 0000000..8b6b6bb
--- /dev/null
+++ b/src/lib/logger.ts
@@ -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
+ }
+}