import { randomUUID } from 'node:crypto'; import { error, fail } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; import { requireUser } from '#lib/server/auth.js'; import { completeCurrent, getLiveView, goBack, issuePresentationAccess, moveOpenUsage, presentUsage, skipCurrent, startOrResumeLive } from '#lib/server/live.js'; import { listShows } from '#lib/server/shows.js'; function text(data: FormData, name: string) { return String(data.get(name) || ''); } function actionError(cause: unknown) { const code = cause instanceof Error ? cause.message : ''; if (code === 'CONFLICT') return { status: 409, error: 'Der Live-Stand wurde bereits geändert. Die Ansicht wurde erneuert.' }; if (code === 'NO_CURRENT') return { status: 409, error: 'Es ist kein aktuelles Piece aktiv.' }; if (code === 'NO_PREVIOUS') return { status: 409, error: 'Es gibt kein vorheriges Piece.' }; if (code === 'LIVE_ORDER_LOCKED') return { status: 409, error: 'Nur offene Pieces hinter dem aktuellen Stand können sortiert werden.' }; if (code === 'SHOW_COMPLETED') return { status: 409, error: 'Abgeschlossene Sendungen können nicht live gesteuert werden.' }; if (code === 'NOT_FOUND') return { status: 404, error: 'Sendung oder Piece wurde nicht gefunden.' }; if (code.startsWith('INVALID_')) return { status: 400, error: 'Die Live-Aktion ist ungültig.' }; return { status: 500, error: 'Die Live-Aktion konnte nicht gespeichert werden.' }; } function attempt(intent: string, operation: () => unknown) { try { return { intent, success: true, result: operation() }; } catch (cause) { const problem = actionError(cause); return fail(problem.status, { intent, ...problem }); } } export const load: PageServerLoad = ({ locals, url }) => { requireUser(locals.user); const showId = url.searchParams.get('show') || listShows()[0]?.id; if (!showId) error(404, 'Keine Sendung vorhanden'); try { return { live: getLiveView(showId), commandId: randomUUID() }; } catch (cause) { if (cause instanceof Error && cause.message === 'NOT_FOUND') error(404, 'Sendung nicht gefunden'); throw cause; } }; export const actions = { start: async ({ locals, request }) => { requireUser(locals.user); const data = await request.formData(); return attempt('start', () => startOrResumeLive(text(data, 'showId'), text(data, 'commandId'), Number(data.get('revision'))) ); }, complete: async ({ locals, request }) => { requireUser(locals.user); const data = await request.formData(); return attempt('complete', () => completeCurrent(text(data, 'showId'), text(data, 'commandId'), Number(data.get('revision'))) ); }, skip: async ({ locals, request }) => { requireUser(locals.user); const data = await request.formData(); return attempt('skip', () => skipCurrent(text(data, 'showId'), text(data, 'commandId'), Number(data.get('revision'))) ); }, back: async ({ locals, request }) => { requireUser(locals.user); const data = await request.formData(); return attempt('back', () => goBack(text(data, 'showId'), text(data, 'commandId'), Number(data.get('revision'))) ); }, present: async ({ locals, request }) => { requireUser(locals.user); const data = await request.formData(); return attempt('present', () => presentUsage( text(data, 'showId'), text(data, 'usageId') || null, text(data, 'commandId'), Number(data.get('revision')) ) ); }, moveUsage: async ({ locals, request }) => { requireUser(locals.user); const data = await request.formData(); return attempt('moveUsage', () => moveOpenUsage(text(data, 'showId'), text(data, 'usageId'), Number(data.get('showRevision')), { sectionId: text(data, 'sectionId') || null, position: Number(data.get('position')) }) ); }, createOutput: async ({ locals, request }) => { requireUser(locals.user); const data = await request.formData(); try { const token = issuePresentationAccess(text(data, 'showId')); return { intent: 'createOutput', success: true, outputUrl: `/heinzlive/${token}` }; } catch (cause) { const problem = actionError(cause); return fail(problem.status, { intent: 'createOutput', ...problem }); } } } satisfies Actions;