Files
klarbild/src/pages/api/jobs/[id]/[action].ts
T
till 28a6049548 Papierkorb statt endgültigem Löschen, und Zugriffsregeln für jede Bild-Route
Löschen in der Bibliothek entfernte Zeile und Ergebnisdatei sofort. Ein
Fehlklick, und ein Ergebnis war weg — samt dem, was es gekostet hat.
Jetzt landet es im Papierkorb:

- weg aus Bibliothek, Freigabelinks, ZIPs, Auslieferung, NAS-Spiegel,
  Druckquellen und Druckverlauf; beim Wiederherstellen unverändert zurück
- „Rückgängig" im Hinweis, Papierkorb-Ansicht mit Resttagen,
  Wiederherstellen, endgültig löschen, leeren
- die Wartung räumt nach settings.trash_days (Standard 30, Admin ->
  Speicher) endgültig ab; 0 schaltet den Papierkorb aus
- ein Bild, das noch in der Warteschlange wartet, wird nicht mehr
  erzeugt (und nicht bezahlt)
- endgültig gelöscht wird nur, was kein anderes Bild mehr nutzt —
  Alternativen teilen ihre Quelle mit dem Original, auch Kombinieren-
  Quellen in source_paths (jsonb)

Beim Durchgehen jeder Abfrage auf items fiel auf: Nur die Datei-Route
prüfte, wer ein Bild sehen darf. Umbenennen, Löschen, Wiederholen,
Alternative, Weiterbearbeiten und die Auswahl-ZIP nahmen jede Kennung von
jedem Konto. Jetzt gilt eine Regel (src/lib/itemaccess.ts): sehen, was
einem gehört oder in der geteilten Bibliothek liegt, ändern nur Eigenes;
Admins alles. Für Till ändert sich nichts. Lea kann fremde Bilder in der
geteilten Bibliothek weiter sehen, aber nicht mehr löschen.

Dazu Favicon und Homebildschirm-Symbol, Hilfe und Changelog-Seite.

Geprüft gegen echtes Postgres mit zwei Konten und echten Dateien:
35 Ende-zu-Ende-Prüfungen bestanden, Oberfläche im Browser (Löschen,
Rückgängig, Papierkorb, Wiederherstellen, 390 px ohne Querlauf).
128 Tests, Typprüfung 0 Fehler.
2026-10-10 06:38:42 +00:00

55 lines
2.5 KiB
TypeScript

import type { APIRoute } from 'astro';
import { one, query } from '../../../../lib/db';
import { enqueue } from '../../../../lib/queue';
export const prerender = false;
const json = (b: unknown, s = 200) =>
new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } });
export const POST: APIRoute = async ({ params, locals }) => {
if (!locals.user) return new Response('Unauthorized', { status: 401 });
const id = params.id!;
const job = await one<{ id: string }>('SELECT id FROM jobs WHERE id=$1', [id]);
if (!job) return json({ error: 'Nicht gefunden.' }, 404);
switch (params.action) {
case 'pause':
await query(`UPDATE jobs SET status='paused' WHERE id=$1`, [id]);
break;
case 'resume': {
await query(`UPDATE jobs SET status='queued' WHERE id=$1`, [id]);
const items = await query<{ id: string }>(
`SELECT id FROM items WHERE job_id=$1 AND status IN ('queued','running')`, [id]);
for (const it of items) await enqueue({ itemId: it.id, jobId: id });
break;
}
case 'cancel':
await query(`UPDATE jobs SET status='cancelled', finished_at=now() WHERE id=$1`, [id]);
await query(`UPDATE items SET status='skipped' WHERE job_id=$1 AND status IN ('queued','running')`, [id]);
break;
case 'retry-failed': {
const items = await query<{ id: string }>(
`UPDATE items SET status='queued', error_message=NULL WHERE job_id=$1 AND status='failed' AND deleted_at IS NULL RETURNING id`, [id]);
await query(`UPDATE jobs SET status='queued', failed_count=0 WHERE id=$1`, [id]);
for (const it of items) await enqueue({ itemId: it.id, jobId: id });
break;
}
case 'delete': {
// Auftrag samt Positionen und deren Objekten entfernen (für alte/fehlgeschlagene Aufträge).
const { deleteObject } = await import('../../../../lib/storage');
const items = await query<any>(
`SELECT source_path, source_paths, result_path, thumb_path FROM items WHERE job_id=$1`, [id]);
for (const it of items) {
const keys = [it.source_path, it.result_path, it.thumb_path, ...((it.source_paths as string[]) || [])].filter(Boolean);
for (const k of keys) await deleteObject(k).catch(() => {});
}
await query(`DELETE FROM items WHERE job_id=$1`, [id]);
await query(`DELETE FROM jobs WHERE id=$1`, [id]);
break;
}
default:
return json({ error: 'Unbekannte Aktion.' }, 400);
}
return json({ ok: true });
};