Adding working board

This commit is contained in:
2026-08-17 09:24:53 +02:00
parent 35685f765a
commit 0237da74cf
18 changed files with 1365 additions and 738 deletions
+12 -8
View File
@@ -10,6 +10,7 @@ import pg from 'pg'
import type { CaseState } from '../src/types.js'
import { authenticateJwt, createDevelopmentAdminToken, hasAdminClaim, requireAdmin } from './auth.js'
import { createLevelRepository } from './levelRepository.js'
import { createObjectStorageFromEnv } from './objectStorage.js'
const { Pool } = pg
const databaseUrl = process.env.DATABASE_URL
@@ -20,7 +21,9 @@ if (!databaseUrl) {
export const pool = new Pool({ connectionString: databaseUrl })
const editingEnabled = process.env.LEVEL_EDITING_ENABLED === 'true'
const levels = createLevelRepository(pool, editingEnabled)
const objectStorage = createObjectStorageFromEnv()
await objectStorage.initialize()
const levels = createLevelRepository(pool, editingEnabled, objectStorage)
function wantsEdit(req: express.Request) {
return editingEnabled && req.query.edit === '1' && hasAdminClaim(req)
@@ -41,7 +44,7 @@ const upload = multer({
})
app.get('/api/health', async (_req, res) => {
try { await pool.query('SELECT 1'); res.json({ ok: true, database: 'connected', schema: 'osint', editingEnabled }) }
try { await pool.query('SELECT 1'); res.json({ ok: true, database: 'connected', objectStorage: objectStorage.provider, schema: 'osint', editingEnabled }) }
catch { res.status(503).json({ ok: false, database: 'unavailable' }) }
})
app.get('/api/session', (req, res) => res.json({ authenticated: Boolean(req.authClaims), isAdmin: hasAdminClaim(req) }))
@@ -89,12 +92,13 @@ app.get('/api/assets/:id', async (req, res, next) => {
try {
const asset = await levels.getAsset(req.params.id)
if (!asset) return res.status(404).json({ error: 'Asset not found' })
const inline = asset.mime_type === 'application/pdf' || asset.mime_type.startsWith('image/') || asset.mime_type.startsWith('text/')
res.setHeader('Content-Type', asset.mime_type || 'application/octet-stream')
res.setHeader('Content-Length', asset.byte_size)
res.setHeader('Content-Disposition', `${inline ? 'inline' : 'attachment'}; filename*=UTF-8''${encodeURIComponent(asset.original_name)}`)
const inline = asset.mimeType === 'application/pdf' || asset.mimeType.startsWith('image/') || asset.mimeType.startsWith('text/')
res.setHeader('Content-Type', asset.mimeType || 'application/octet-stream')
res.setHeader('Content-Length', asset.byteSize)
res.setHeader('Content-Disposition', `${inline ? 'inline' : 'attachment'}; filename*=UTF-8''${encodeURIComponent(asset.originalName)}`)
res.setHeader('X-Content-Type-Options', 'nosniff')
res.send(asset.content)
asset.stream.on('error', next)
asset.stream.pipe(res)
} catch (error) { next(error) }
})
app.post('/api/levels/:id/documents', requireAdmin, upload.single('file'), async (req, res, next) => {
@@ -113,7 +117,7 @@ app.get('/api/levels/:id', async (req, res, next) => {
})
app.put('/api/levels/:id', async (req, res, next) => {
const state = req.body as CaseState
if (!state || state.id !== req.params.id || !Array.isArray(state.evidence) || !Array.isArray(state.connections)) return res.status(400).json({ error: 'Invalid level state' })
if (!state || state.id !== req.params.id || !Array.isArray(state.exhibits) || !Array.isArray(state.views) || !Array.isArray(state.connections)) return res.status(400).json({ error: 'Invalid level state' })
try {
const authorMode = wantsEdit(req)
await levels.saveLevel(state, authorMode)