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.
This commit is contained in:
till committed 2026-10-10 06:38:42 +00:00
1 parent ee887cc29c
commit 28a6049548
33 files changed
+456 -40

No files matched your search

+7 -1
View File
@@ -14,7 +14,13 @@ export const POST: APIRoute = async ({ request, locals }) => {
if (!ids.length) return new Response('Keine Auswahl.', { status: 400 });
const rows = await query<{ id: string; result_path: string | null; filename: string | null }>(
`SELECT id, result_path, filename FROM items WHERE id = ANY($1) AND result_path IS NOT NULL`, [ids]);
// Nur Bilder, die dieses Konto sehen darf, und keine aus dem Papierkorb.
// Vorher landete jede Kennung jedes Kontos im Archiv.
`SELECT i.id, i.result_path, i.filename FROM items i JOIN jobs j ON j.id = i.job_id
WHERE i.id = ANY($1::uuid[]) AND i.result_path IS NOT NULL AND i.deleted_at IS NULL
AND ($3::boolean OR j.created_by = $2
OR (NOT j.private AND (SELECT library_visibility FROM settings WHERE id=1) = 'shared'))`,
[ids.filter((x) => /^[0-9a-f-]{36}$/i.test(String(x))), locals.user.uid, locals.user.role === 'admin']);
if (!rows.length) return new Response('Keine Ergebnisse.', { status: 404 });
const archive = new ZipArchive({ zlib: { level: 6 } });