feat: Bibliothek + Admin

- Bibliothek: grid, before/after slider, rename, download, folder assign, delete, multi-select
- Admin: stats+costs, OpenRouter key (masked/encrypted), budget, defaults, Picdrop config+test,
  models (list/add from OpenRouter/set default/delete), users (add/set password)
- api: items/:id PATCH+DELETE, folders CRUD, admin/{settings,stats,models,test-picdrop,users}
- lib/picdrop.ts (SFTP/FTPS test+upload, temp->rename)
This commit is contained in:
2026-07-23 15:22:12 +00:00
parent dce7515c9b
commit 25d0a5cd97
13 changed files with 621 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
import type { APIRoute } from 'astro';
import { one, 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' } });
export const PATCH: APIRoute = async ({ params, request, locals }) => {
if (!locals.user) return new Response('Unauthorized', { status: 401 });
const b = await request.json();
const sets: string[] = []; const args: any[] = [];
if (typeof b.filename === 'string') { args.push(b.filename.trim()); sets.push(`filename=$${args.length}`); }
if ('folder_id' in b) { args.push(b.folder_id || null); sets.push(`folder_id=$${args.length}`); }
if (!sets.length) return json({ error: 'Nichts zu ändern.' }, 400);
args.push(params.id);
const row = await one(`UPDATE items SET ${sets.join(',')} WHERE id=$${args.length} RETURNING id, filename, folder_id`, args);
return json({ item: row });
};
export const DELETE: APIRoute = async ({ params, locals }) => {
if (!locals.user) return new Response('Unauthorized', { status: 401 });
const it = await one<{ result_path: string | null }>('SELECT result_path FROM items WHERE id=$1', [params.id]);
if (it?.result_path) await deleteObject(it.result_path).catch(() => {});
await query('DELETE FROM items WHERE id=$1', [params.id]);
return json({ ok: true });
};