import fs from 'node:fs/promises'; import path from 'node:path'; import { SESSION_DIR } from './config.js'; // id -> meta. Backed by data/sessions//meta.json so restarts keep decks. const sessions = new Map(); export async function initSessions() { await fs.mkdir(SESSION_DIR, { recursive: true }); const entries = await fs.readdir(SESSION_DIR, { withFileTypes: true }); for (const entry of entries) { if (!entry.isDirectory()) continue; try { const raw = await fs.readFile(path.join(SESSION_DIR, entry.name, 'meta.json'), 'utf8'); const meta = JSON.parse(raw); // A deck still "processing" when the server died can never finish; mark it. if (meta.status === 'processing') { meta.status = 'error'; meta.error = 'Processing was interrupted by a restart. Please re-upload.'; } sessions.set(meta.id, meta); } catch { // Skip unreadable session folders rather than crash on boot. } } } export function getSession(id) { return sessions.get(id); } export function listSessions() { return [...sessions.values()].sort((a, b) => b.createdAt - a.createdAt); } export function sessionDir(id) { return path.join(SESSION_DIR, id); } export function outDir(id) { return path.join(SESSION_DIR, id, 'out'); } export async function saveSession(meta) { sessions.set(meta.id, meta); const dir = sessionDir(meta.id); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(path.join(dir, 'meta.json'), JSON.stringify(meta, null, 2)); return meta; } export async function deleteSession(id) { sessions.delete(id); await fs.rm(sessionDir(id), { recursive: true, force: true }); } // Fields safe to expose to an audience member (no presenter token). export function publicMeta(meta) { if (!meta) return null; return { id: meta.id, title: meta.title, kind: meta.kind, status: meta.status, error: meta.error || null, viewerFile: meta.viewerFile || null, createdAt: meta.createdAt }; }