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)} /> -