Files
klarbild/src/pages/api/items/index.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

43 lines
2.1 KiB
TypeScript

import type { APIRoute } from 'astro';
import { one, query } from '../../../lib/db';
export const prerender = false;
const json = (b: unknown, s = 200) =>
new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } });
// Bibliothek mit Filtern: ?task=&format=&folder=&delivery=&by=
export const GET: APIRoute = async ({ url, locals }) => {
if (!locals.user) return new Response('Unauthorized', { status: 401 });
const q = url.searchParams;
const s = await one<any>('SELECT library_visibility, anonymous_generations FROM settings WHERE id=1');
const isAdmin = locals.user.role === 'admin';
const shared = s?.library_visibility === 'shared';
// Private Aufträge nie in der Bibliothek; Sichtbarkeit nach Einstellung.
const where: string[] = [`i.status='done'`, `j.private=false`, `i.deleted_at IS NULL`];
const args: any[] = [];
const add = (cond: string, val: any) => { args.push(val); where.push(cond.replace('?', `$${args.length}`)); };
if (!isAdmin && !shared) add('j.created_by = ?', locals.user.uid);
if (q.get('folder')) add('i.folder_id = ?', q.get('folder'));
if (q.get('tag')) add('i.color_tag = ?', q.get('tag'));
if (q.get('delivery')) add('i.delivery_status = ?', q.get('delivery'));
if (q.get('by')) add('j.created_by = ?', q.get('by'));
if (q.get('format')) add('i.output_px = ?', q.get('format'));
if (q.get('task')) {
args.push(JSON.stringify([q.get('task')]));
where.push(`j.recipe_snapshot->'tasks' @> $${args.length}::jsonb`);
}
const rows = await query(
`SELECT i.id, i.filename, i.output_px, i.has_alpha, i.result_path, i.thumb_path, i.folder_id, i.color_tag,
i.delivery_status, i.nas_status, i.prompt_used, i.created_at, i.model_used, i.variant_of, j.mode,
${(s?.anonymous_generations && !isAdmin) ? 'NULL' : 'u.display_name'} AS by_name,
j.recipe_snapshot->'tasks' AS tasks
FROM items i
JOIN jobs j ON j.id = i.job_id
LEFT JOIN users u ON u.id = j.created_by
WHERE ${where.join(' AND ')}
ORDER BY i.created_at DESC LIMIT 400`, args);
return json({ items: rows });
};