feat: add app_config table with runtime log level control and POS structured logging
- New app_config key-value table for system settings, with in-memory cache (mirrors ModuleService pattern) - GET/PATCH /v1/config endpoints for reading and updating config (settings.view/settings.edit permissions) - Runtime log level: PATCH /v1/config/log_level applies immediately, persists across restarts - Startup loads log level from DB in onReady hook (env var is default, DB overrides) - Add structured request.log.info() to POS routes: transaction create/complete/void, drawer open/close, discount create/update/delete Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
48
packages/backend/src/routes/v1/config.ts
Normal file
48
packages/backend/src/routes/v1/config.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { FastifyPluginAsync } from 'fastify'
|
||||
import { AppConfigService } from '../../services/config.service.js'
|
||||
import { AppConfigUpdateSchema, LogLevel } from '@lunarfront/shared/schemas'
|
||||
|
||||
export const configRoutes: FastifyPluginAsync = async (app) => {
|
||||
app.get('/config', { preHandler: [app.authenticate, app.requirePermission('settings.view')] }, async (_request, reply) => {
|
||||
const configs = await AppConfigService.getAll(app.db)
|
||||
return reply.send({ data: configs })
|
||||
})
|
||||
|
||||
app.get('/config/:key', { preHandler: [app.authenticate, app.requirePermission('settings.view')] }, async (request, reply) => {
|
||||
const { key } = request.params as { key: string }
|
||||
const configs = await AppConfigService.getAll(app.db)
|
||||
const entry = configs.find((c) => c.key === key)
|
||||
if (!entry) return reply.status(404).send({ error: { message: 'Config key not found', statusCode: 404 } })
|
||||
return reply.send(entry)
|
||||
})
|
||||
|
||||
app.patch('/config/:key', { preHandler: [app.authenticate, app.requirePermission('settings.edit')] }, async (request, reply) => {
|
||||
const { key } = request.params as { key: string }
|
||||
const parsed = AppConfigUpdateSchema.safeParse(request.body)
|
||||
if (!parsed.success) {
|
||||
return reply.status(400).send({ error: { message: 'Validation failed', details: parsed.error.flatten(), statusCode: 400 } })
|
||||
}
|
||||
|
||||
const value = parsed.data.value === null ? null : String(parsed.data.value)
|
||||
|
||||
// Key-specific validation
|
||||
if (key === 'log_level') {
|
||||
const levelResult = LogLevel.safeParse(value)
|
||||
if (!levelResult.success) {
|
||||
return reply.status(400).send({
|
||||
error: { message: 'Invalid log level. Must be one of: fatal, error, warn, info, debug, trace', statusCode: 400 },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const updated = await AppConfigService.set(app.db, key, value)
|
||||
|
||||
// Apply log level change immediately
|
||||
if (key === 'log_level' && value) {
|
||||
app.log.level = value
|
||||
request.log.info({ level: value, changedBy: request.user.id }, 'Log level changed')
|
||||
}
|
||||
|
||||
return reply.send(updated)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user