Adding fixes that lets us pull a myster in the db into a json file

This commit is contained in:
2026-08-23 21:37:06 +02:00
parent 3c099a5daa
commit 7dec2994b4
8 changed files with 719 additions and 290 deletions
+29
View File
@@ -15,6 +15,9 @@ export type UploadedDocument = { buffer: Buffer; originalname: string; mimetype:
export type AssetRecord = { original_name: string; mime_type: string; byte_size: string; content: Buffer | null; storage_provider: 'postgres' | 's3'; object_key: string | null }
export type AssetResponse = { originalName: string; mimeType: string; byteSize: number; stream: NodeJS.ReadableStream }
export type TemplateSummary = { id: string; slug: string; name: string; currentVersion: number; versionCount: number; updatedAt: string }
// A frozen template version's board, read back for round-trip export. `state.goals`
// carries the goals (in author mode); match/semantic rules are read alongside.
export type TemplateBoardExport = { slug: string; name: string; version: number; state: CaseState; matchRules: EvidenceMatchRuleDefinition[]; semanticRules: EvidenceSemanticRuleDefinition[] }
export type EvidenceMatchRuleInput = {
name: string
sourceLabel?: string
@@ -51,6 +54,7 @@ export interface LevelRepository {
listTemplates(): Promise<TemplateSummary[]>
instantiateTemplate(templateSlug: string, input: { id: string; title?: string; version?: number }): Promise<CaseState | null>
saveLevelAsTemplate(levelId: string, input: { slug: string; name: string }): Promise<TemplateSummary | null>
getTemplateBoard(versionId: string): Promise<TemplateBoardExport | null>
getLevel(levelId: string, authorMode?: boolean): Promise<CaseState | null>
saveLevel(state: CaseState, authorMode: boolean): Promise<void>
resetLevel(levelId: string): Promise<CaseState | null>
@@ -347,6 +351,14 @@ export function createLevelRepository(pool: Pool, editingEnabled: boolean, objec
async function assembleLevel(slug: string, authorMode = false): Promise<CaseState | null> {
const level = await findLevel(pool, slug)
if (!level) return null
return assembleBoard(level, authorMode)
}
// Assemble a board's CaseState from a level row (id + board_id). Split out from
// assembleLevel so a frozen template version's board can be read the same way (its
// board_id backs the same tables); player-state reads (flags/seen) return empty for
// a template board that no player has instantiated.
async function assembleBoard(level: LevelRow, authorMode = false): Promise<CaseState> {
const [exhibitsResult, blocksResult, regionsResult, membershipsResult, connectionsResult, metadataResult, eventEvidenceResult,
aliasesResult, partyEvidenceResult, briefResult, conceptsResult, viewsResult, requirementsResult, flagsResult, seenResult] = await Promise.all([
pool.query<ExhibitRow>(`SELECT e.id,e.exhibit_type_id,e.xpos,e.ypos,e.width,e.height,e.rotation,e.z_index,e.hidden,
@@ -739,6 +751,23 @@ export function createLevelRepository(pool: Pool, editingEnabled: boolean, objec
} catch (error) { await client.query('ROLLBACK'); throw error } finally { client.release() }
return templateSummary(input.slug)
},
async getTemplateBoard(versionId) {
const version = (await pool.query<{ board_id: string; title: string; subtitle: string; version: number; slug: string; name: string }>(
`SELECT v.board_id,v.title,v.subtitle,v.version,t.slug,t.name FROM osint.level_template_versions v
JOIN osint.level_templates t ON t.id=v.template_id WHERE v.id=$1`, [versionId])).rows[0]
if (!version) return null
// A synthetic level row over the version's board: id has no player rows (flags/seen
// resolve empty); viewport/revision/status are placeholders the exporter discards.
const level: LevelRow = {
id: versionId, slug: version.slug, board_id: version.board_id, title: version.title, subtitle: version.subtitle,
status: 'template', viewport_x: 0, viewport_y: 28, viewport_zoom: 0.7, updated_at: new Date(), revision: '0',
source_template_version_id: null,
}
const [state, matchRules, semanticRules] = await Promise.all([
assembleBoard(level, true), evidenceMatchRules(pool, version.board_id, true), evidenceSemanticRules(pool, version.board_id, true),
])
return { slug: version.slug, name: version.name, version: version.version, state, matchRules, semanticRules }
},
getLevel(levelId, authorMode = false) { return assembleLevel(levelId, authorMode) },
async saveLevel(state, authorMode) {
let persistedState = state