Adding migrations and lot of work. Starting work on the demo scope

This commit is contained in:
2026-08-22 14:53:23 +02:00
parent 1893bf23af
commit 94ccbfd1b9
29 changed files with 1280 additions and 122 deletions
+20
View File
@@ -47,6 +47,8 @@ export interface NarrativeRepository {
createPlaythrough(userId: string, mysterySlug?: string): Promise<PlaythroughState | null>
getCurrentPlaythrough(userId: string): Promise<PlaythroughState | null>
advancePlaythrough(userId: string, playthroughId: string, terminalKey?: string): Promise<{ ok: boolean; state?: PlaythroughState; error?: string }>
listAchievements(playthroughId: string): Promise<string[] | null>
awardAchievement(playthroughId: string, flagKey: string, nodeId?: string | null): Promise<{ ok: boolean; earned: boolean; error?: string }>
listMysteries(): Promise<MysterySummary[]>
deleteMystery(id: string): Promise<boolean>
uploadAsset(file: UploadedFile): Promise<AssetDto>
@@ -231,6 +233,24 @@ export function createNarrativeRepository(pool: Pool, objectStorage: ObjectStora
return row ? stateForPlaythrough(row.id) : null
},
async listAchievements(playthroughId) {
if (!(await pool.query('SELECT 1 FROM osint.playthroughs WHERE id=$1', [playthroughId])).rowCount) return null
const rows = (await pool.query<{ flag_key: string }>('SELECT flag_key FROM osint.achievements WHERE playthrough_id=$1 ORDER BY flag_key', [playthroughId])).rows
return rows.map(row => row.flag_key)
},
// Grant an achievement (idempotent). `earned` is true only on the first grant.
// The eventual server-side rule engine calls this same operation.
async awardAchievement(playthroughId, rawKey, nodeId) {
const key = rawKey.trim()
if (!/^[a-z][a-z0-9_.-]{0,63}$/.test(key)) return { ok: false, earned: false, error: 'Invalid achievement key' }
if (!(await pool.query('SELECT 1 FROM osint.playthroughs WHERE id=$1', [playthroughId])).rowCount) return { ok: false, earned: false, error: 'Playthrough not found' }
const result = await pool.query(
'INSERT INTO osint.achievements (playthrough_id,flag_key,awarded_by_node_id) VALUES ($1,$2,$3) ON CONFLICT (playthrough_id,flag_key) DO NOTHING',
[playthroughId, key, nodeId || null])
return { ok: true, earned: (result.rowCount || 0) > 0 }
},
async advancePlaythrough(userId, playthroughId, terminalKey) {
const client = await pool.connect()
try {