a593dea6c5
renderCell brings a relative crop onto an exact physical size; missing bleed pixels are copied from the edge instead of stretching the motif. Without a crop it falls back to a centred cover crop, never a distorted fill. buildSheetPdf writes a PDF page that is exactly the sheet size in mm with hairline crop marks and an optional footer. Source resolution guards uploads to the sources/ prefix and reuses the library visibility rules.
45 lines
1.9 KiB
TypeScript
45 lines
1.9 KiB
TypeScript
// 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<Buffer> {
|
|
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 || '');
|
|
if (!/^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<any>(
|
|
`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}`;
|
|
}
|