feat: mirror-all-to-NAS, content reset (danger zone), in-app changelog + CHANGELOG.md, How-To refresh

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XNQ8ghPfzAfsyVYd6HgFb6
This commit is contained in:
2026-07-23 20:52:48 +00:00
parent 79454c0b92
commit 3a098d708c
7 changed files with 191 additions and 5 deletions
+36
View File
@@ -0,0 +1,36 @@
import type { APIRoute } from 'astro';
import { query } from '../../../lib/db';
import { deleteObject } from '../../../lib/storage';
export const prerender = false;
const json = (b: unknown, s = 200) =>
new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } });
/** Setzt Inhalte zurück: löscht alle Bilder/Aufträge/Ordner + zugehörige Dateien.
* Nutzer, Einstellungen, Modelle und Rezepte bleiben erhalten.
* Erfordert { confirm: 'RESET' }. */
export const POST: APIRoute = async ({ request, locals }) => {
if (locals.user?.role !== 'admin') return new Response('Forbidden', { status: 403 });
const b = await request.json().catch(() => ({} as any));
if (b.confirm !== 'RESET') return json({ error: 'Bestätigung fehlt (confirm=RESET).' }, 400);
// Zuerst alle Objekte aus dem Speicher entfernen.
const items = await query<any>('SELECT source_path, source_paths, result_path, thumb_path FROM items');
let objects = 0;
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(() => {}); objects++; }
}
// Inhalts-Tabellen leeren (Konfiguration bleibt).
await query('DELETE FROM print_job_items');
await query('DELETE FROM print_jobs');
await query('DELETE FROM share_links');
await query('DELETE FROM telegram_drafts');
await query('DELETE FROM items');
await query('DELETE FROM jobs');
await query('DELETE FROM folders');
return json({ ok: true, deleted_items: items.length, deleted_objects: objects });
};
+2 -1
View File
@@ -1,5 +1,5 @@
import type { APIRoute } from 'astro';
import { storageStats, runRetention, purgeSources } from '../../../lib/maintenance';
import { storageStats, runRetention, purgeSources, mirrorAllToNas } from '../../../lib/maintenance';
import { one } from '../../../lib/db';
export const prerender = false;
@@ -12,6 +12,7 @@ export const GET: APIRoute = async () => json({ stats: await storageStats() });
export const POST: APIRoute = async ({ request }) => {
const b = await request.json().catch(() => ({}));
if (b.action === 'purge_sources') return json(await purgeSources());
if (b.action === 'mirror_all') return json(await mirrorAllToNas());
if (b.action === 'retention') {
const s = await one<{ retention_days: number | null }>('SELECT retention_days FROM settings WHERE id=1');
if (!s?.retention_days) return json({ error: 'Keine Aufbewahrungsfrist gesetzt.' }, 400);