florian.wildeandClaude Sonnet 5 3b5fbd1bcd feat: Update-Skript fuer Linux-Deployments (update.sh)
Stoppt den Docker-Stack, sichert lokale Aenderungen an
docker-compose.yml per git stash (falls vorhanden), fuehrt git pull
aus, spielt die Aenderungen zurueck und startet den Stack neu im
Build-Modus. Bricht bei git-pull- oder Stash-Konflikten kontrolliert
ab statt mit einem halb aktualisierten Stand weiterzumachen.

Version 1.8.1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 00:05:56 +02:00
2026-07-17 17:13:05 +02:00
2026-07-17 17:13:05 +02:00

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, Git-Tags markieren die jeweiligen Versionsstände (git tag). Schritt-für-Schritt- Anleitung für die Inbetriebnahme auf einem neuen Server: DEPLOYMENT.md.

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; RSS-Feed (/feed.xml, Icon im Kopfbereich) mit einem Eintrag pro Tag zum Abonnieren
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 per Suche Tagen/Essenszeiten zuweisen (mehrere pro Zelle) und wieder entfernen; globale Allergen-/Zusatzstofflisten pflegen (ebenfalls mehrsprachig); eingereichte Rezeptvorschläge sichten, übernehmen oder ablehnen (Anzahl offener Vorschläge als Badge in der Navigation); 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
  feed.xml/              RSS-Feed des Speiseplans (öffentlich)
  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):

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:

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).

Umgebungsvariablen (Live-Betrieb)

Variable Bedeutung Geheim?
DATABASE_URL Verbindung zur PostgreSQL-Datenbank, z. B. postgres://postgres:<passwort>@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@<Hostname aus APP_URL> 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):

docker compose --env-file .env.local up -d --build
# App: Port 3000 (hinter Reverse-Proxy mit HTTPS betreiben)
# PostgreSQL: kein Host-Port, nur intern im Compose-Netzwerk erreichbar

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):

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.

S
Description
No description provided
Readme
465 KiB
Languages
TypeScript 94.3%
JavaScript 2.1%
PLpgSQL 1.6%
CSS 1.1%
Shell 0.5%
Other 0.4%