70 lines
2.0 KiB
JavaScript
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
|
||
|
|
};
|
||
|
|
}
|