feat: scaffold klarbild foundation (astro+postgres+s3, auth, migrations, health)

- Astro 5 SSR (node standalone) + React, OKLCH tokens (no tailwind)
- migrations/001_init.sql: full schema per 03-datenmodell-api
- lib: db+migrations, crypto (AES-256-GCM), auth (argon2+signed session, ratelimit),
  storage (S3/MinIO, presigned URLs), openrouter (POST /v1/images, cost)
- middleware: init-once + session guard + admin gate; /api/health (db+storage)
- login + studio placeholder; seeds (till/lea, default recipes); Dockerfile
- verified: astro build passes
This commit is contained in:
2026-07-23 11:11:08 +00:00
commit b46dbbe889
26 changed files with 9651 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
import { defineMiddleware } from 'astro:middleware';
import { ensureInit } from './lib/init';
import { readSession } from './lib/auth';
const PUBLIC_PATHS = [/^\/login/, /^\/api\/auth\/login/, /^\/api\/health/, /^\/g\//, /^\/api\/telegram\/webhook/];
export const onRequest = defineMiddleware(async (ctx, next) => {
// Health/Webhook dürfen laufen, auch wenn Init noch hakt — sonst blockiert nichts.
try { await ensureInit(); } catch (e) { if (ctx.url.pathname !== '/api/health') throw e; }
const user = readSession(ctx.request.headers.get('cookie'));
ctx.locals.user = user;
const path = ctx.url.pathname;
const isPublic = PUBLIC_PATHS.some((r) => r.test(path));
if (!isPublic && !user) {
if (path.startsWith('/api/')) return new Response('Unauthorized', { status: 401 });
return ctx.redirect('/login');
}
if (path.startsWith('/api/admin') || path.startsWith('/admin')) {
if (user?.role !== 'admin') {
if (path.startsWith('/api/')) return new Response('Forbidden', { status: 403 });
return ctx.redirect('/');
}
}
return next();
});