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:
till committed 2026-07-23 15:22:12 +00:00
1 parent dce7515c9b
commit 25d0a5cd97
13 files changed
+621

No files matched your search

+28
View File
@@ -0,0 +1,28 @@
import type { APIRoute } from 'astro';
import { one, query } from '../../../lib/db';
import { hashPassword } from '../../../lib/auth';
export const prerender = false;
const json = (b: unknown, s = 200) =>
new Response(JSON.stringify(b), { status: s, headers: { 'Content-Type': 'application/json' } });
export const GET: APIRoute = async () => {
const users = await query('SELECT id, username, role, display_name, created_at FROM users ORDER BY username');
return json({ users });
};
// POST: neuen Nutzer anlegen ODER Passwort setzen (bei vorhandenem username/id).
export const POST: APIRoute = async ({ request }) => {
const b = await request.json();
if (b.id && b.password) {
await query('UPDATE users SET password_hash=$2 WHERE id=$1', [b.id, await hashPassword(b.password)]);
return json({ ok: true });
}
if (!b.username || !b.password) return json({ error: 'username + password nötig.' }, 400);
const exists = await one('SELECT id FROM users WHERE username=$1', [b.username.trim()]);
if (exists) return json({ error: 'Benutzername vergeben.' }, 400);
const row = await one(`INSERT INTO users (username, role, display_name, password_hash)
VALUES ($1,$2,$3,$4) RETURNING id, username, role, display_name`,
[b.username.trim(), b.role === 'admin' ? 'admin' : 'user', b.display_name || b.username.trim(), await hashPassword(b.password)]);
return json({ user: row });
};