Trash instead of deleting for good — and access checks on every image route
Deleting an image in the library removed the row and the result file on the spot. One slip and a result was gone, including what it had cost to generate. Now a delete moves the image into the trash: - It disappears from the library, from every share link, from ZIPs, from deliveries, the backup mirror, print sources and the print history — and comes back unchanged when restored. - The toast offers "Undo" for seven seconds. A trash view lists what is in there with the days left, restore, delete for good, empty. - Maintenance removes images for good after `settings.trash_days` (default 30, set in Admin -> Storage). 0 switches the trash off. - An image trashed while it is still queued is skipped by the worker instead of being generated — and billed — for nobody. - Deleting for good removes only files no other image still uses. An alternative version shares its source with the original; before, the retention cleanup could take the source of the other one with it. This includes combine sources, which live in jsonb, not in an array. While going through every query that touches images, it turned out that only the file route checked who may see an image. Renaming, deleting, retrying, generating an alternative, reusing and zipping took any id from any signed-in account. On a single-user install that changes nothing; on a family install one account could delete another's images. One rule now lives in src/lib/itemaccess.ts: read your own (or everything in a shared library, never a private session), change only your own; admins all. A refused id answers 404 like a missing one. Also: a favicon and an iPhone home-screen icon. There was none, so the tab showed a blank page and /favicon.ico bounced to the login. Verified against a real Postgres with two accounts and real files on disk: 35 end-to-end checks (access, share links, restore, purge with shared sources, expiry through maintenance, trash switched off). In the browser, English and German: delete, undo, trash view, restore, no horizontal scroll at 390 px. Unit tests: 130 (4 new).
This commit is contained in:
31 files changed
+435
-39
No files matched your search
@@ -5,6 +5,7 @@
|
||||
".md on": ".md an",
|
||||
"0 = unlimited.": "0 = unbegrenzt.",
|
||||
"1 Sep 2026": "01.09.2026",
|
||||
"1 day left": "Noch 1 Tag",
|
||||
"1 image": "1 Bild",
|
||||
"1 year": "1 Jahr",
|
||||
"1 year from now": "1 Jahr ab jetzt",
|
||||
@@ -218,6 +219,7 @@
|
||||
"Assign images to this folder from the folder menu.": "Weise Bildern über das Ordner-Menü diesen Ordner zu.",
|
||||
"Automated tests for the geometry plus a raster sample of the finished PDFs at 254 dpi (= 10 px/mm) with edge measurement.": "Automatische Tests für die Geometrie plus Rasterprobe der fertigen PDFs bei 254 dpi (= 10 px/mm) mit Kantenmessung.",
|
||||
"Automation / MCP access": "Automatisierung / MCP-Zugriff",
|
||||
"Back to the library": "Zurück zur Bibliothek",
|
||||
"Back up all existing images to the mirror": "Alle vorhandenen Bilder auf den Sicherungsspiegel sichern",
|
||||
"Back up all finished images to every backup target (mirror + marked targets)?": "Alle fertigen Bilder auf alle Backup-Ziele (Sicherungsspiegel + markierte Ziele) sichern?",
|
||||
"Back up all finished images to the mirror?": "Alle fertigen Bilder auf den Sicherungsspiegel sichern?",
|
||||
@@ -349,6 +351,7 @@
|
||||
"Delete": "Löschen",
|
||||
"Delete folder": "Ordner löschen",
|
||||
"Delete folder? The images are kept.": "Ordner löschen? Die Bilder bleiben erhalten.",
|
||||
"Delete for good": "Endgültig löschen",
|
||||
"Delete items older than {days} days now?": "Positionen älter als {days} Tage jetzt löschen?",
|
||||
"Delete preset": "Vorlage löschen",
|
||||
"Delete preset?": "Vorlage löschen?",
|
||||
@@ -357,7 +360,12 @@
|
||||
"Delete target?": "Ziel löschen?",
|
||||
"Delete the link \"{name}\" for good?\n\nIt cannot be opened afterwards and the access figures are gone. Just switching it off is usually enough.": "Den Link „{name}\" endgültig löschen?\n\nEr lässt sich danach nicht mehr öffnen und die Zugriffszahlen sind weg. Nur abschalten reicht meistens.",
|
||||
"Delete the source images of all finished items? Results are kept.": "Alle Quellbilder fertiger Positionen löschen? Ergebnisse bleiben erhalten.",
|
||||
"Delete this image for good? This cannot be undone.": "Dieses Bild endgültig löschen? Das lässt sich nicht rückgängig machen.",
|
||||
"Delete this job?": "Diesen Auftrag löschen?",
|
||||
"Delete {n} images for good? This cannot be undone.": "{n} Bilder endgültig löschen? Das lässt sich nicht rückgängig machen.",
|
||||
"Deleted for good.": "Endgültig gelöscht.",
|
||||
"Deleted images stay here for {days} days and can be restored until then. They are hidden from the library, from every share link and from every delivery.": "Gelöschte Bilder bleiben {days} Tage hier und lassen sich bis dahin wiederherstellen. In der Bibliothek, in Freigabelinks und bei der Auslieferung tauchen sie nicht mehr auf.",
|
||||
"Deleted images wait this long in the trash and can be restored until then. 0 = no trash, a delete is final.": "So lange bleiben gelöschte Bilder im Papierkorb und lassen sich bis dahin wiederherstellen. 0 = kein Papierkorb, Löschen ist endgültig.",
|
||||
"Deleted.": "Gelöscht.",
|
||||
"Deletes all images, jobs and folders irreversibly, so you can start over cleanly. Users, settings, models and preset recipes are kept. Tip: run “Back up all existing images to the mirror” first.": "Löscht alle Bilder, Aufträge und Ordner unwiderruflich, um sauber neu zu starten. Nutzer, Einstellungen, Modelle und Rezepte bleiben erhalten. Tipp: vorher „Alle Bilder auf den Sicherungsspiegel sichern\".",
|
||||
"Deliver": "Ausliefern",
|
||||
@@ -388,6 +396,8 @@
|
||||
"Email address and notifications.": "E-Mail-Adresse und Benachrichtigungen.",
|
||||
"Empty = everyone with the link gets in": "Leer = jeder mit dem Link kommt rein",
|
||||
"Empty = username. It has to belong to the mailbox, otherwise recipients reject the mail.": "Leer = Benutzername. Muss zum Postfach gehören, sonst lehnen Empfänger die Mail ab.",
|
||||
"Empty the trash": "Papierkorb leeren",
|
||||
"Empty the trash? {n} images will be deleted for good.": "Papierkorb leeren? {n} Bilder werden endgültig gelöscht.",
|
||||
"Enable the backup mirror and save first.": "Sicherungsspiegel erst aktivieren & speichern.",
|
||||
"Enter a host and save first.": "Erst Host eintragen und speichern.",
|
||||
"Enter a new key to replace it": "Neuen Key eingeben zum Wechseln",
|
||||
@@ -523,7 +533,9 @@
|
||||
"Monthly budget ($)": "Monatliches Budget ($)",
|
||||
"More output targets (FTP/SFTP)": "Weitere Ausgabeziele (FTP/SFTP)",
|
||||
"More settings": "Mehr einstellen",
|
||||
"Move to the trash": "In den Papierkorb",
|
||||
"Moved into the folder.": "In Ordner verschoben.",
|
||||
"Moved to the trash.": "In den Papierkorb gelegt.",
|
||||
"My account": "Mein Konto",
|
||||
"My account · Klarbild": "Mein Konto · Klarbild",
|
||||
"My account, sharing & email": "Mein Konto, Teilen & E-Mail",
|
||||
@@ -689,6 +701,7 @@
|
||||
"Remove frame, wall and shop interface.": "Rahmen, Wand und Shop-Oberfläche entfernen.",
|
||||
"Remove {images}?": "{images} entfernen?",
|
||||
"Remove {name}": "{name} entfernen",
|
||||
"Removed at the next cleanup": "Wird beim nächsten Aufräumen entfernt",
|
||||
"Removed from the folder.": "Aus Ordner entfernt.",
|
||||
"Rename folder": "Ordner umbenennen",
|
||||
"Rename folder:": "Ordner umbenennen:",
|
||||
@@ -699,6 +712,9 @@
|
||||
"Reset library & jobs": "Bibliothek & Aufträge zurücksetzen",
|
||||
"Resetting …": "Setze zurück …",
|
||||
"Resolution (dpi)": "Auflösung (dpi)",
|
||||
"Restore": "Wiederherstellen",
|
||||
"Restore all": "Alle wiederherstellen",
|
||||
"Restored.": "Wiederhergestellt.",
|
||||
"Results land under base folder / YYYY-MM /. Credentials are stored encrypted.": "Ergebnisse landen unter Basisordner / JJJJ-MM /. Zugangsdaten werden verschlüsselt gespeichert.",
|
||||
"Resume": "Fortsetzen",
|
||||
"Retention (days)": "Aufbewahrung (Tage)",
|
||||
@@ -834,6 +850,8 @@
|
||||
"The problem": "Das Problem",
|
||||
"The same image once more — for a second format, say": "Dasselbe Bild noch einmal — z. B. für ein zweites Format",
|
||||
"The second way out for large sizes: the image is spread across several sheets. The tiles overlap by a glue flap, which is printed on both neighbouring sheets and marked with a dashed line. Every sheet carries its number with row and column.": "Der zweite Weg für große Maße: Das Bild wird auf mehrere Blätter verteilt. Die Kacheln überlappen sich um einen Klebefalz, der auf beiden Nachbarblättern gedruckt und gestrichelt markiert wird. Jedes Blatt trägt seine Nummer samt Reihe und Spalte.",
|
||||
"The trash is empty": "Der Papierkorb ist leer",
|
||||
"The trash is switched off — deleting is final.": "Der Papierkorb ist abgeschaltet — Löschen ist endgültig.",
|
||||
"The two entries do not match.": "Die beiden Eingaben stimmen nicht überein.",
|
||||
"The usual route runs through a layout program or through the printer driver, and both of them mean too well: “fit to page”, “scale to fit”, “scale borderless”. What comes out is a print that is almost right. For a passport photo, “almost” is too little, and using a whole A4 sheet for a 4 × 3 snapshot, only because it would otherwise not fit on the page at 100% size, is plain waste.": "Der übliche Weg führt über ein Layoutprogramm oder über den Druckertreiber, und beide meinen es zu gut: „an Seitengröße anpassen“, „einpassen“, „randlos skalieren“. Am Ende liegt ein Ausdruck vor, der fast stimmt. Für ein Passbild ist „fast“ zu wenig, und für ein 4 × 3-Bildchen ein ganzes DIN-A4-Blatt zu verbrauchen, nur weil es sonst nicht in 100 % Größe auf die Seite passt, ist schlicht Verschwendung.",
|
||||
"The whole image stays visible, with a border around it": "Ganzes Bild bleibt sichtbar, außen entsteht ein Rand",
|
||||
@@ -855,12 +873,16 @@
|
||||
"Today ${today} · 7 days ${week}": "Heute ${today} · 7 Tage ${week}",
|
||||
"Too large": "Zu groß",
|
||||
"Too many attempts. Please try again in a quarter of an hour.": "Zu viele Versuche. Bitte in einer Viertelstunde noch einmal.",
|
||||
"Trash (days)": "Papierkorb (Tage)",
|
||||
"Trash emptied.": "Papierkorb geleert.",
|
||||
"Trash · {n}": "Papierkorb · {n}",
|
||||
"True to size": "Maßhaltig",
|
||||
"Try again": "Noch einmal versuchen",
|
||||
"US visa 51 × 51 mm (2 × 2 in)": "Visum USA 51 × 51 mm (2 × 2 in)",
|
||||
"Under <b>“File names”</b> the existing library can be brought onto the unique naming scheme. <b>“Check library”</b> first shows what would change and how many names are currently used twice; <b>“Rename now”</b> carries it out. The image files themselves are left untouched — they sit in storage under their id, not under their name. A second run changes nothing. <b>“Deliver again”</b> then uploads every already delivered image under its new name; the old files stay on the delivery target and you clear them away there yourself.": "Unter <b>„Dateinamen\"</b> lässt sich die vorhandene Bibliothek auf das eindeutige Namensschema bringen. <b>„Bibliothek prüfen\"</b> zeigt zuerst, was sich ändern würde und wie viele Namen aktuell doppelt vergeben sind; <b>„Jetzt umbenennen\"</b> setzt es um. Die Bilddateien selbst bleiben dabei unangetastet — sie liegen im Speicher unter ihrer Kennung, nicht unter ihrem Namen. Ein zweiter Lauf ändert nichts mehr. <b>„Erneut ausliefern\"</b> lädt danach alle schon ausgelieferten Bilder unter dem neuen Namen erneut hoch; die alten Dateien bleiben am Ziel liegen und räumst du dort selbst weg.",
|
||||
"Under the hood": "Unter der Haube",
|
||||
"Under the hood: a real cost cap, reliable job completion, a reachability check for the mirror.": "Unter der Haube: echter Kostendeckel, verlässlicher Auftragsabschluss, Erreichbarkeits-Diagnose für den Spiegel.",
|
||||
"Undo": "Rückgängig",
|
||||
"Unique file names — images no longer overwrite each other": "Eindeutige Dateinamen — Bilder überschreiben sich nicht mehr",
|
||||
"Unit": "Einheit",
|
||||
"Unlink": "Trennen",
|
||||
@@ -1015,6 +1037,8 @@
|
||||
"{images} on this sheet": "{images} auf diesem Bogen",
|
||||
"{n} copies to backup targets, {failed} errors.": "{n} Kopien auf Backup-Ziele, {failed} Fehler.",
|
||||
"{n} copies to backup targets.": "{n} Kopien auf Backup-Ziele.",
|
||||
"{n} days left": "Noch {n} Tage",
|
||||
"{n} deleted.": "{n} gelöscht.",
|
||||
"{n} entries in {path}": "{n} Einträge in {path}",
|
||||
"{n} extra sheet": "{n} Extrabogen",
|
||||
"{n} extra sheets": "{n} Extrabogen",
|
||||
@@ -1029,6 +1053,7 @@
|
||||
"{n} images renamed, {ok} were already fine.": "{n} Bilder umbenannt, {ok} waren schon in Ordnung.",
|
||||
"{n} leftovers removed from “{folder}”.": "{n} Reste in „{folder}\" entfernt.",
|
||||
"{n} min ago": "vor {n} min",
|
||||
"{n} moved to the trash.": "{n} in den Papierkorb gelegt.",
|
||||
"{n} names were used twice": "{n} Namen waren doppelt vergeben",
|
||||
"{n} of {checked} images would get a new name — {dupes} names are currently used twice.": "{n} von {checked} Bildern bekämen einen neuen Namen — {dupes} Namen sind aktuell doppelt vergeben.",
|
||||
"{n} of {checked} images would get a new name.": "{n} von {checked} Bildern bekämen einen neuen Namen.",
|
||||
|
||||
Reference in new issue
Block a user