Implement Scene 7 evidence goal flow
This commit is contained in:
+72
-3
@@ -10,6 +10,7 @@ import pg from 'pg'
|
||||
import type { CaseState } from '../src/types.js'
|
||||
import { authenticateJwt, createDevelopmentAdminToken, hasAdminClaim, requireAdmin, resolveUserId } from './auth.js'
|
||||
import { createLevelRepository } from './levelRepository.js'
|
||||
import { createEvidenceJudgeFromEnv } from './evidenceJudge.js'
|
||||
import { createNarrativeRepository } from './narrativeRepository.js'
|
||||
import { createTextExtractorFromEnv } from './ocr.js'
|
||||
import { createStoryGraphRepository, type StoryNodeType } from './storyGraphRepository.js'
|
||||
@@ -27,7 +28,8 @@ const editingEnabled = process.env.LEVEL_EDITING_ENABLED === 'true'
|
||||
const objectStorage = createObjectStorageFromEnv()
|
||||
await objectStorage.initialize()
|
||||
const textExtractor = createTextExtractorFromEnv()
|
||||
const levels = createLevelRepository(pool, editingEnabled, objectStorage)
|
||||
const evidenceJudge = createEvidenceJudgeFromEnv()
|
||||
const levels = createLevelRepository(pool, editingEnabled, objectStorage, evidenceJudge)
|
||||
const narrative = createNarrativeRepository(pool, objectStorage)
|
||||
const storyGraph = createStoryGraphRepository(pool)
|
||||
const STORY_NODE_TYPES: StoryNodeType[] = ['cutscene', 'dialogue', 'level', 'det_gate', 'llm_gate']
|
||||
@@ -51,7 +53,8 @@ const upload = multer({
|
||||
})
|
||||
|
||||
app.get('/api/health', async (_req, res) => {
|
||||
try { await pool.query('SELECT 1'); res.json({ ok: true, database: 'connected', objectStorage: objectStorage.provider, textExtraction: textExtractor.provider, schema: 'osint', editingEnabled }) }
|
||||
try { await pool.query('SELECT 1'); res.json({ ok: true, database: 'connected', objectStorage: objectStorage.provider, textExtraction: textExtractor.provider,
|
||||
evidenceJudge: evidenceJudge.enabled ? evidenceJudge.provider : 'disabled', 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) }))
|
||||
@@ -118,6 +121,15 @@ app.post('/api/levels/:id/documents', upload.single('file'), async (req, res, ne
|
||||
document ? res.status(201).json(document) : res.status(404).json({ error: 'Level not found' })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
app.post('/api/levels/:id/documents/:documentId/judge', async (req, res, next) => {
|
||||
try {
|
||||
if (!hasAdminClaim(req) && !await narrative.ownsActiveLevel(resolveUserId(req), String(req.params.id))) {
|
||||
return res.status(403).json({ error: 'This level is not active for the current player' })
|
||||
}
|
||||
const result = await levels.judgeDocument(String(req.params.id), String(req.params.documentId))
|
||||
result ? res.json(result) : res.status(404).json({ error: 'Level or document not found' })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
app.post('/api/levels/:id/reveals/seen', async (req, res, next) => {
|
||||
try {
|
||||
const ids = Array.isArray(req.body?.documentIds) ? req.body.documentIds.map(String) : []
|
||||
@@ -199,6 +211,62 @@ app.delete('/api/levels/:id/evidence-match-rules/:ruleId', requireAdmin, async (
|
||||
removed ? res.json({ ok: true }) : res.status(404).json({ error: 'Evidence match rule not found' })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
app.get('/api/levels/:id/goals', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const goals = await levels.listGoals(String(req.params.id))
|
||||
goals ? res.json(goals) : res.status(404).json({ error: 'Level not found' })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
app.post('/api/levels/:id/goals', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
if (!requireEditing(res)) return
|
||||
const goal = await levels.createGoal(String(req.params.id), req.body)
|
||||
goal ? res.status(201).json(goal) : res.status(404).json({ error: 'Level not found' })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
app.put('/api/levels/:id/goals/:goalId', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
if (!requireEditing(res)) return
|
||||
const goal = await levels.updateGoal(String(req.params.id), String(req.params.goalId), req.body)
|
||||
goal ? res.json(goal) : res.status(404).json({ error: 'Level or goal not found' })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
app.delete('/api/levels/:id/goals/:goalId', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
if (!requireEditing(res)) return
|
||||
const removed = await levels.deleteGoal(String(req.params.id), String(req.params.goalId))
|
||||
if (removed === null) return res.status(404).json({ error: 'Level not found' })
|
||||
removed ? res.json({ ok: true }) : res.status(404).json({ error: 'Goal not found' })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
app.get('/api/levels/:id/evidence-semantic-rules', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const rules = await levels.listEvidenceSemanticRules(String(req.params.id))
|
||||
rules ? res.json(rules) : res.status(404).json({ error: 'Level not found' })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
app.post('/api/levels/:id/evidence-semantic-rules', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
if (!requireEditing(res)) return
|
||||
const rule = await levels.createEvidenceSemanticRule(String(req.params.id), req.body)
|
||||
rule ? res.status(201).json(rule) : res.status(404).json({ error: 'Level not found' })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
app.put('/api/levels/:id/evidence-semantic-rules/:ruleId', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
if (!requireEditing(res)) return
|
||||
const rule = await levels.updateEvidenceSemanticRule(String(req.params.id), String(req.params.ruleId), req.body)
|
||||
rule ? res.json(rule) : res.status(404).json({ error: 'Level or semantic rule not found' })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
app.delete('/api/levels/:id/evidence-semantic-rules/:ruleId', requireAdmin, async (req, res, next) => {
|
||||
try {
|
||||
if (!requireEditing(res)) return
|
||||
const removed = await levels.deleteEvidenceSemanticRule(String(req.params.id), String(req.params.ruleId))
|
||||
if (removed === null) return res.status(404).json({ error: 'Level not found' })
|
||||
removed ? res.json({ ok: true }) : res.status(404).json({ error: 'Semantic rule not found' })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
app.get('/api/admin/mysteries', requireAdmin, async (_req, res, next) => {
|
||||
try { res.json(await narrative.listMysteries()) } catch (error) { next(error) }
|
||||
})
|
||||
@@ -403,7 +471,8 @@ app.get('/api/playthroughs/current', async (req, res, next) => {
|
||||
app.post('/api/playthroughs/:id/advance', async (req, res, next) => {
|
||||
try {
|
||||
const result = await narrative.advancePlaythrough(resolveUserId(req), String(req.params.id), req.body?.terminalKey)
|
||||
result.ok ? res.json(result.state ?? null) : res.status(result.error === 'Playthrough not found' ? 404 : 400).json({ error: result.error })
|
||||
result.ok ? res.json(result.state ?? null) : res.status(result.error === 'Playthrough not found' ? 404 : result.errorCode === 'goals_incomplete' ? 409 : 400)
|
||||
.json({ error: result.error, errorCode: result.errorCode, pendingGoals: result.pendingGoals })
|
||||
} catch (error) { next(error) }
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user