Fundament: db/migrations/0001_init.sql (users/sessions/passkey_credentials/
webauthn_challenges + Inhaltstabellen ohne RLS), db/migrate.mjs
(idempotenter Migrationsrunner), lib/db (pg-Pool + query-Wrapper),
lib/auth/password.ts (Node-crypto.scrypt, keine neue Abhängigkeit),
lib/auth/session.ts (opake DB-Session-Tokens, Sliding-Refresh in proxy.ts).
Passkeys: @simplewebauthn/server + /browser ersetzen Supabase's
experimentelles WebAuthn-API (dafür gab es bisher kein eigenes
Credential-Schema); RP-Konfiguration kommt jetzt direkt aus APP_URL statt
über scripts/configure-passkeys.mjs und die Supabase-Management-API.
scripts/bootstrap-admin.mjs legt bei leerer users-Tabelle automatisch
einen Admin mit zufälligem Passwort an und hängt die Zugangsdaten an
ZUGANGSDATEN.md an (idempotent, via predev/prestart-Hooks aufgerufen).
Bugfix im Zuge des Umbaus: "mind. 1 Passkey nötig, um Passwort-Login zu
deaktivieren" wurde bisher nur clientseitig geprüft – jetzt zusätzlich
serverseitig in PATCH /api/account.
E-Mail-Änderung im eigenen Konto läuft jetzt direkt nach
Passwort-Bestätigung statt über eine Supabase-Bestätigungsmail (kein
SMTP-Server vorhanden).
Rest der Anwendung (Gerichte/Essenszeiten/Kennzeichnung/Vorschläge/
Benutzerverwaltung/Branding) nutzt noch den alten Supabase-Client –
folgt in weiteren Commits.
Der Kopfbereich der öffentlichen Speisekarte zeigte seinen eigenen
LanguageSwitcher auch auf schmalen Bildschirmen an, obwohl
PublicMobileMenu dort bereits einen eigenen rendert. Der Header-Switcher
ist jetzt wie der Rest des Desktop-Layouts erst ab dem sm-Breakpoint
sichtbar.
Betrifft UI-Texte (DE/EN/PL), Route (/intern/bereiche ->
/intern/essenszeiten), Code-Kommentare, Changelog und README.
Fachlich unverändert (weiterhin die Tabelle/Komponente "sections"/
SectionManager im Code), da "Essenszeiten" den Zeitbezug des Features
klarer ausdrückt als der bisherige Sammelbegriff.
Statt den Button immer anzuzeigen und erst nach Klick auf die
fehlende Konfiguration hinzuweisen, wird er serverseitig anhand von
process.env.DEEPL_API_KEY ein-/ausgeblendet (Gerichte, Allergene,
Zusatzstoffe). Die dadurch überflüssige "translateUnavailable"-
Meldung wurde aus allen drei Sprachen entfernt.
Migration 0006: sections.key bekommt einen automatisch generierten
Schlüssel (wie dishes.id), menu_entries.section_key kaskadiert beim
Löschen eines Bereichs, neue RLS-Policy erlaubt Kantinenmitarbeitern/
Admins Schreibzugriff auf sections.
Neue Seite „Bereiche" im Kantinen-Bereich (SectionManager): Bereiche
anlegen, umbenennen (inkl. EN/PL), Uhrzeit setzen, löschen und per
Pfeil-Buttons neu anordnen.
Nicht angemeldete Besucher können unter /vorschlag ein fehlendes
Gericht vorschlagen (Name + optionale Beschreibung); Dublettenprüfung
gegen bestehende Gerichte, einfacher Honeypot gegen Bot-Spam. Staff/
Admin sichten eingereichte Vorschläge unter /intern/vorschlaege und
übernehmen sie als Gericht oder lehnen sie ab.
Alle Seiten und Komponenten (öffentliche Speisekarte, Login, Kantinen-
und Admin-Bereich, eigenes Konto) verwenden jetzt die Wörterbücher aus
lib/i18n statt fest codierter deutscher Texte. Gerichte/Bereiche/
Allergene/Zusatzstoffe zeigen den Namen zur aktuell gewählten Sprache;
Gerichte- und Kennzeichnungs-Formulare bekommen Englisch-/Polnisch-
Felder mit Übersetzen-Button (siehe vorherigen Commit). Datumsformat-
ierung (formatDateLong) nimmt jetzt ein locale-Argument statt fix
'de-DE'.
app/api/translate ruft DeepL für EN+PL auf und schreibt das Ergebnis
in die jeweilige _en/_pl-Spalte (Staff/Admin-only, Service-Role-Key).
Ohne DEEPL_API_KEY (neue Docker-Variable) antwortet die Route mit
configured:false, das Frontend zeigt dann den Hinweis zur manuellen
Eingabe statt eines Fehlers.
Wörterbücher unter lib/i18n statt next-intl (Next.js 16 ist neu genug,
dass eine externe Routing-Middleware zusätzliches Risiko bedeutet
hätte); Sprache liegt in einem Cookie, kein URL-Präfix nötig, dadurch
bleibt proxy.ts unverändert. LocaleProvider stellt Wörterbuch + Sprache
per Context für Client-Komponenten bereit (lib/i18n/shared.ts ist
bewusst getrennt von lib/i18n/index.ts, damit next/headers nicht in
den Client-Bundle gelangt).
Absolute Positionierung im Header kollidierte mit Logos in breitem
Seitenverhältnis (z. B. Banner-Format), da das Logo bei h-20 nahezu
bildschirmbreit gerendert wird. Anmelden-Link steht jetzt auf eigener
Zeile über dem Logo statt absolut positioniert in der oberen Ecke.
- SITE_TITLE (Default "Canteen Menu") steuert Browser-Tab-Titel und
H1-Fallback ohne Logo
- Favicon-Upload analog zum Logo über den Design-Bereich (Storage-
Bucket branding); generateMetadata setzt icons.icon jetzt immer
explizit, da Next.js sonst app/favicon.ico zusätzlich einbindet –
die Datei liegt daher als expliziter Fallback unter public/
Schalter im eigenen Konto, erzwingt vorher mindestens einen
registrierten Passkey (sonst Aussperrung). Durchsetzung beim Login:
nach erfolgreicher Passwort-Prüfung wird bei deaktiviertem Flag sofort
wieder abgemeldet; Passkey-Anmeldung bleibt davon unberührt.
Chip-Auswahl im Gericht-Formular (Klick zum An-/Abwählen) statt
Checkbox-Liste; Kürzel erscheinen hochgestellt neben dem Gerichtnamen
in Speisekarte, mobiler Ansicht und Wochenplan-Editor. Editor bekommt
außerdem einen Heute-Button.
- "Heute" springt direkt zur aktuellen Woche
- Unter dem sm-Breakpoint umschaltbar zwischen Tag (Standard) und
Woche statt der Tabelle; Desktop bleibt vollständig serverseitig
gerendert, nur der mobile Umschalter ist eine Client-Komponente
Nach dem Abmelden landete man auf 0.0.0.0:3000, weil die absolute
Redirect-URL aus request.url gebaut wurde und dort seit der Bindung an
0.0.0.0 die Server-Adresse statt des aufgerufenen Hosts steht.
Logout leitet jetzt relativ weiter; der Proxy baut die URL aus dem
Host-Header des Browsers (inkl. x-forwarded-proto hinter Proxies).
Eine Leiste für alles: Gruppe "Speiseplan" (Wochenplan, Gerichte,
Allergene & Zusatzstoffe) und für Admins zusätzlich Gruppe
"Verwaltung" (Benutzer, Design). Aktiver Menüpunkt wird hervorgehoben,
die Konto-Seite nutzt denselben Header – kein Hin- und Herspringen
mehr zwischen Kantinen-Bereich und Verwaltung.
- next dev/start binden jetzt explizit an 0.0.0.0 statt nur localhost
(Docker war über die Port-Freigabe bereits erreichbar, jetzt auch
beim nativen npm-Betrieb); docker-compose.yml macht das Binden an
alle Interfaces zusätzlich explizit
- package.json-Name, Docker-Image/Container/Netzwerk-Namen, lokaler
DB-Name (canteen_menu), Browser-Tab-Titel und README auf den
englischen Projektnamen "canteen-menu" umgestellt; deutsche
In-App-Texte für die Kantinen-Mitarbeiter bleiben unverändert
- Supabase-Projekt (Anzeigename) und native lokale Postgres-DB
entsprechend umbenannt (Ref/URL/Keys unverändert)
- Klick auf den Benutzernamen im Header (Kantinen- und Admin-Bereich)
öffnet /konto
- Name-Änderung per RLS-Policy mit spaltenscharfem Grant (nur
full_name), Rolle bleibt ausschließlich über die Admin-API änderbar
- E-Mail-Änderung über supabase.auth.updateUser mit Bestätigungslink;
Trigger synchronisiert profiles.email nach Bestätigung
- Passwortänderung verlangt zur Verifizierung das aktuelle Passwort
(Re-Login-Check) plus Wiederholung des neuen Passworts
- Passkey-Verwaltung aus /intern/konto hierher verschoben
- X-Frame-Options/CSP frame-ancestors gegen Clickjacking,
nosniff, Referrer-Policy, Permissions-Policy
- Origin-Check als CSRF-Defense-in-Depth für /api/admin/users
und /logout (zusätzlich zu SameSite=Lax-Cookies und JSON-Content-Type)