// Auflösen der Bildquellen für den Druckbogen: frisch hochgeladen oder aus der Bibliothek. import { one } from './db'; import { getObject } from './storage'; export type PrintSource = | { kind: 'upload'; path: string } | { kind: 'item'; id: string }; export interface SessionUser { uid: string | null; role: string } /** Lädt die Bilddaten und prüft dabei die Zugriffsrechte wie /api/items/:id/file. */ export async function loadSource(src: PrintSource, user: SessionUser): Promise { if (!src || typeof src !== 'object') throw new Error('Quelle fehlt.'); if (src.kind === 'upload') { // Nur der Upload-Bereich ist erreichbar — kein Weg zu results/ oder /etc. const p = String(src.path || ''); // \n bewusst ausschließen: JS-„$" matcht auch vor einem abschließenden Zeilenumbruch. if (/[\r\n]/.test(p) || !/^sources\/[0-9]{4}\/[A-Za-z0-9_-]+\.[A-Za-z0-9]{2,5}$/.test(p)) throw new Error('Ungültige Quelle.'); return getObject(p); } if (src.kind === 'item') { const item = await one( `SELECT i.result_path, i.filename, j.created_by, j.private FROM items i JOIN jobs j ON j.id = i.job_id WHERE i.id = $1`, [src.id]); if (!item?.result_path) throw new Error('Bild nicht gefunden.'); const isAdmin = user.role === 'admin'; const own = item.created_by === user.uid; if (!isAdmin && !own) { const s = await one<{ library_visibility: string }>('SELECT library_visibility FROM settings WHERE id=1'); if (item.private || s?.library_visibility !== 'shared') throw new Error('Kein Zugriff auf dieses Bild.'); } return getObject(item.result_path); } throw new Error('Unbekannte Quellenart.'); } /** Sprechender Dateiname für den Download. */ export function sheetFilename(prefix: string, ext: string): string { const date = new Date().toISOString().slice(0, 10); const slug = (prefix || 'druckbogen').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40); return `${date}_${slug || 'druckbogen'}.${ext}`; }