Files
Presentation-system/server/sessions.js
T
2026-09-13 20:23:05 +01:00

70 lines
2.0 KiB
JavaScript

import fs from 'node:fs/promises';
import path from 'node:path';
import { SESSION_DIR } from './config.js';
// id -> meta. Backed by data/sessions/<id>/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
};
}