# Canteen Menu – Speisekarte für die Betriebskantine Digitale Speisekarte für eine Firmenkantine (ca. 250 Mitarbeiter) mit öffentlicher Wochenansicht, Kantinen-Bereich zur Pflege der Gerichte und Admin-Bereich für Benutzerverwaltung und Branding. **Stack:** Next.js 16 (App Router, TypeScript, Tailwind CSS) · selbst gehostetes PostgreSQL (via `pg`, eigene Migrationen) · eigene Session-Auth (Passwort via `node:crypto` scrypt, Passkeys via `@simplewebauthn`) · DeepL als einzige externe API (optional). Läuft vollständig selbst gehostet, keine Cloud-Abhängigkeit außer optional DeepL. Änderungen je Version stehen in [CHANGELOG.md](CHANGELOG.md), Git-Tags markieren die jeweiligen Versionsstände (`git tag`). ## Funktionen | Bereich | Funktionen | | --- | --- | | Öffentlich (ohne Login) | Wochenplan Mo–Fr mit frei konfigurierbaren Essenszeiten; Navigation vorherige/nächste Woche/Heute; auf schmalen Bildschirmen umschaltbare Tages-/Wochenansicht (Tag ist Standard); Logo zentriert über der Karte; Allergen-/Zusatzstoff-Kürzel je Gericht, Legende unterhalb der Karte; Sprachumschalter Deutsch/Englisch/Polnisch; Rezeptvorschläge einreichen | | Kantinen-Team (`/intern`) | Essenszeiten anlegen/bearbeiten/löschen/neu anordnen; Gerichte anlegen/bearbeiten/löschen (inkl. Live-Suche, Mehrfachauswahl zum Batch-Löschen, englischem/polnischem Namen mit automatischer DeepL-Übersetzung) mit Allergenen/Zusatzstoffen je Gericht; Gerichte Tagen/Essenszeiten zuweisen (mehrere pro Zelle) und wieder entfernen; globale Allergen-/Zusatzstofflisten pflegen (ebenfalls mehrsprachig); eingereichte Rezeptvorschläge sichten, übernehmen oder ablehnen; eigenes Konto verwalten (Name, E-Mail, Passwort, Passkeys, Passwort-Anmeldung an/aus) | | Admin (`/admin`) | Kantinenmitarbeiter anlegen/bearbeiten/löschen (inkl. Passwort-Reset mit Wiederholung und Rollenvergabe); Farbgebung der Speisekarte anpassen; Logo und Favicon hochladen/entfernen | Anmeldung mit E-Mail + Passwort oder alternativ per **Passkey** (Registrierung im eigenen Konto nach dem ersten Login). Die Passwort-Anmeldung kann pro Konto deaktiviert werden, sobald mindestens ein Passkey registriert ist (serverseitig erzwungen). Die gesamte Oberfläche ist auf Deutsch, Englisch und Polnisch verfügbar (Sprachumschalter, Cookie-basiert). Gerichte, Essenszeiten, Allergene und Zusatzstoffe lassen sich zusätzlich auf Englisch/Polnisch benennen – mit optionaler automatischer Übersetzung über DeepL (siehe `DEEPL_API_KEY` unten) und jederzeit manuell korrigierbar. ## Projektstruktur ``` app/ Next.js-Routen page.tsx Öffentliche Speisekarte login/ Login (Passwort + Passkey) vorschlag/ Öffentliches Rezeptvorschlag-Formular intern/ Kantinen-Bereich (Rolle: staff oder admin) vorschlaege/ Rezeptvorschläge sichten/übernehmen admin/ Verwaltung (Rolle: admin) branding/[filename]/ Logo/Favicon ausliefern (öffentlich) api/auth/ Login, Logout, Passkeys api/account/ Eigenes Konto (Name, E-Mail, Passwort, Toggle) api/admin/users/ Benutzerverwaltung (nur Admin) api/admin/branding/ Logo-/Favicon-Upload (nur Admin) api/dishes/, api/sections/, api/allergens/, api/additives/, api/menu-entries/, api/dish-suggestions/, api/settings/ CRUD-Routen für den Kantinen-/Admin-Bereich api/translate/ DeepL-Anbindung (serverseitig) api/locale/ Sprach-Cookie setzen components/ Client-Komponenten (WeekEditor, DishManager, …) components/i18n/ Sprachkontext, Sprachumschalter lib/i18n/ Wörterbücher (de/en/pl), Locale-Helfer lib/auth/ Sessions, Passwort-Hashing, WebAuthn-Konfiguration lib/db/ pg-Pool, Query-Helfer, gemeinsame Ladefunktionen lib/branding.ts Lokale Datei-Ablage für Logo/Favicon lib/ Typen, Datums-/Formatierungshelfer proxy.ts Session-Prüfung + Schutz von /intern, /admin, /konto db/migrations/ Datenbankschema (reines SQL, nummeriert) db/migrate.mjs Wendet neue Migrationen an (idempotent, bei jedem Start) scripts/bootstrap-admin.mjs Legt bei leerer Datenbank automatisch einen Admin mit Zufallspasswort an (siehe ZUGANGSDATEN.md) ``` ## Entwicklung Voraussetzung in beiden Varianten: `.env.local` mit den Werten aus `.env.example` (liegt lokal bereits vor und ist per `.gitignore` vom Repo ausgeschlossen). **Variante A – Docker (nichts vorinstallieren außer Docker):** ```bash docker compose --env-file .env.local --profile dev up dev # http://localhost:3000 (Hot-Reload, Quellcode wird gemountet) ``` **Variante B – lokal installiertes Node.js + PostgreSQL:** ```bash npm install npm run dev # http://localhost:3000 (bindet an 0.0.0.0, auch im LAN erreichbar) ``` In beiden Varianten wird die Datenbank beim Start automatisch auf den neuesten Stand migriert und – falls noch kein Benutzer existiert – ein Admin-Konto mit Zufallspasswort angelegt (siehe [Zugangsdaten](#zugangsdaten)). ## Umgebungsvariablen (Live-Betrieb) | Variable | Bedeutung | Geheim? | | --- | --- | --- | | `DATABASE_URL` | Verbindung zur PostgreSQL-Datenbank, z. B. `postgres://postgres:@db:5432/canteen_menu` | **ja** | | `APP_URL` | Öffentliche URL der App, z. B. `https://menu.example.de` (Origin-Check hinter Reverse-Proxy; zugleich Passkey-RP-Domain – wird direkt daraus abgeleitet, kein separater Konfigurationsschritt nötig) | nein | | `SITE_TITLE` | Titel im Browser-Tab und Überschrift ohne Logo, Default `Canteen Menu` | nein | | `ADMIN_EMAIL` | Optional: E-Mail des automatisch angelegten Admin-Erstnutzers (nur relevant, solange die Datenbank leer ist). Ohne Angabe: `admin@` | nein | | `DEEPL_API_KEY` | Optional: aktiviert „Automatisch übersetzen“ für Gerichte/Allergene/Zusatzstoffe (DeepL, kostenloser Free-Tier). Ohne Key funktioniert alles weiter, nur automatisch übersetzen ist deaktiviert | **ja – niemals im Browser/Repo!** | Build & Start auf dem Server – **mit Docker** (empfohlen, Node.js und PostgreSQL müssen nicht installiert sein): ```bash docker compose --env-file .env.local up -d --build # App: Port 3000 (hinter Reverse-Proxy mit HTTPS betreiben) # PostgreSQL: Port 5433 ``` Vor dem ersten Start: eine leere `ZUGANGSDATEN.md` im Projektverzeichnis anlegen (`touch ZUGANGSDATEN.md`), da diese Datei per Bind-Mount in den Container eingebunden wird und die automatisch generierten Admin- Zugangsdaten dort landen. Oder klassisch ohne Docker (mit lokal erreichbarem PostgreSQL): ```bash npm ci npm run build npm run start # Port 3000, bindet an 0.0.0.0 (hinter Reverse-Proxy betreiben) ``` Passkeys sind an die Domain gebunden (RP-ID = Hostname aus `APP_URL`). Vor dem Livegang `APP_URL` auf die öffentliche URL setzen – ein zusätzlicher Konfigurationsschritt ist nicht nötig. Bereits registrierte Passkeys einer vorherigen Domain verlieren dabei ihre Gültigkeit und müssen neu registriert werden. ## Datenbank Reines, selbst gehostetes PostgreSQL (keine Row-Level-Security-Policies) – alle Zugriffsrechte werden in den Route-Handlern geprüft (`lib/auth/session.ts`, `requireRole`). - Schema: `db/migrations/*.sql`, nummeriert, wird von `db/migrate.mjs` automatisch angewendet (idempotent, bei jedem Start des Servers bzw. Docker-Containers). - Neue Migration hinzufügen: `db/migrations/000N_beschreibung.sql` anlegen, wird beim nächsten Start automatisch eingespielt. - Manuell migrieren: `npm run db:migrate` Rechte-Modell (in Anwendungscode statt Datenbank-RLS): - Öffentlich lesbar (kein Login nötig): Speisekarte, Essenszeiten, Allergene, Zusatzstoffe, Logo/Favicon. - `staff`/`admin` pflegen Gerichte, Essenszeiten, Wochenplan und Kennzeichnungen. - Nur `admin` ändert Einstellungen (Farben/Logo) und verwaltet Benutzer. - Logo/Favicon liegen als Dateien in `BRANDING_DIR` (Docker-Volume `branding_data`), öffentlich ausgeliefert über `/branding/[filename]`. ## Zugangsdaten Beim allerersten Start mit leerer Datenbank legt `scripts/bootstrap-admin.mjs` automatisch einen Admin-Benutzer mit zufällig generiertem Passwort an und schreibt die Zugangsdaten nach `ZUGANGSDATEN.md` – diese Datei ist bewusst **nicht** im Repository (`.gitignore`). Nach dem ersten Login bitte das Startpasswort ändern.