feat: add app_config table with runtime log level control and POS structured logging
All checks were successful
CI / ci (pull_request) Successful in 20s
CI / e2e (pull_request) Successful in 56s

- 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:
ryan
2026-04-04 18:56:21 +00:00
parent 51e7902ee2
commit 772d5578ad
11 changed files with 154 additions and 0 deletions

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