florian.wilde 72459d8da5 Konto: Passwort-Anmeldung pro Nutzer deaktivierbar
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.
2026-07-18 00:46:40 +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) · Supabase (PostgreSQL, Auth inkl. Passkeys, Storage, Row Level Security)

Funktionen

Bereich Funktionen
Öffentlich (ohne Login) Wochenplan Mo–Fr mit den Bereichen Brotzeit (09:00–10:00), Mittagessen, Mittagessen vegetarisch und Suppe (je 11:30–13:30); Navigation vorherige/nächste Woche; Logo zentriert über der Karte; Allergene & Zusatzstoffe unterhalb der Karte
Kantinen-Team (/intern) Gerichte anlegen/bearbeiten/löschen; Gerichte Tagen/Bereichen zuweisen (mehrere pro Zelle) und wieder entfernen; globale Allergen-/Zusatzstofflisten pflegen; eigene Passkeys verwalten
Admin (/admin) Kantinenmitarbeiter anlegen/bearbeiten/löschen (inkl. Passwort-Reset und Rollenvergabe); Farbgebung der Speisekarte anpassen; Logo hochladen/entfernen

Anmeldung mit E-Mail + Passwort oder alternativ per Passkey (Registrierung im Bereich „Passkeys“ nach dem ersten Login).

Projektstruktur

app/                    Next.js-Routen
  page.tsx              Öffentliche Speisekarte
  login/                Login (Passwort + Passkey)
  intern/               Kantinen-Bereich (Rolle: staff oder admin)
  admin/                Verwaltung (Rolle: admin)
  api/admin/users/      Benutzerverwaltung (serverseitig, Service-Role-Key)
components/             Client-Komponenten (WeekEditor, DishManager, …)
lib/supabase/           Supabase-Clients (Browser / Server / Admin)
lib/                    Typen, Datums-/Formatierungshelfer
proxy.ts                Session-Refresh + Schutz von /intern und /admin
supabase/migrations/    Schema & Seed-Daten (Referenz/Doku)
supabase/local/         Skripte + Dumps für die lokale PostgreSQL-Kopie
scripts/                Hilfsskripte (DB-Migration, lokaler Sync)

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:

npm install
npm run dev            # http://localhost:3000 (bindet an 0.0.0.0, auch im LAN erreichbar)

Umgebungsvariablen (Live-Betrieb)

Beim Webserver/Hoster müssen genau diese drei Variablen hinterlegt werden (Werte: Supabase-Dashboard → Project Settings → API Keys):

Variable Bedeutung Geheim?
NEXT_PUBLIC_SUPABASE_URL URL des Supabase-Projekts, z. B. https://hvzikwjoyhoakedlywhj.supabase.co nein
NEXT_PUBLIC_SUPABASE_ANON_KEY Öffentlicher anon-Key (RLS schützt die Daten) nein
SUPABASE_SERVICE_ROLE_KEY Service-Role-Key für die Benutzerverwaltung ja – niemals im Browser/Repo!
APP_URL Öffentliche URL der App, z. B. https://menu.example.de (Origin-Check hinter Reverse-Proxy; zugleich Passkey-Domain) nein

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)
# Lokale DB-Kopie: Port 5433 (Dienst "db", optional)

Oder klassisch ohne Docker:

npm ci
npm run build
npm run start          # Port 3000, bindet an 0.0.0.0 (hinter Reverse-Proxy betreiben)

Passkeys für die Produktions-Domain freischalten

Passkeys sind an die Domain gebunden. Aktuell ist localhost konfiguriert. Vor dem Livegang: APP_URL auf die öffentliche URL setzen und einmalig

SUPABASE_ACCESS_TOKEN=<personal-access-token> node scripts/configure-passkeys.mjs

ausführen (liest APP_URL aus der Umgebung; die URL kann alternativ als Argument übergeben werden). Ohne lokales Node.js geht es auch über Docker:

docker run --rm -v .:/app -w /app \
  -e SUPABASE_ACCESS_TOKEN=<token> -e APP_URL=https://menu.example.de \
  node:24-alpine node scripts/configure-passkeys.mjs

Achtung: Bereits registrierte Passkeys der alten Domain verlieren dabei ihre Gültigkeit und müssen neu registriert werden.

Datenbank

Supabase (führende Datenbank)

  • Projekt-Ref: hvzikwjoyhoakedlywhj (Region eu-central-1, Frankfurt)
  • Schema und Seed-Daten: supabase/migrations/0001_init.sql und 0002_seed.sql
  • Ausführen weiterer SQL-Dateien: SUPABASE_ACCESS_TOKEN=<token> node scripts/db-apply.mjs <datei.sql>

Sicherheitsmodell (Row Level Security):

  • anon darf ausschließlich lesen (Speisekarte, Bereiche, Allergene, Zusatzstoffe, Einstellungen).
  • staff/admin (Rolle im profiles-Eintrag) pflegen Gerichte, Wochenplan und Kennzeichnungen.
  • Nur admin ändert Einstellungen (Farben/Logo) und verwaltet Benutzer; das Anlegen/Löschen von Benutzern läuft serverseitig über den Service-Role-Key (app/api/admin/users).
  • Storage-Bucket branding (Logo): öffentlich lesbar, Schreibzugriff nur Admin.

Lokale PostgreSQL-Kopie

Die Datenbank canteen_menu (Kopie von Schema + Daten aus Supabase, inkl. Minimal-Stubs für auth/storage) gibt es in zwei Varianten:

Per Docker: Der Dienst db aus docker-compose.yml spielt die Skripte aus supabase/local/ beim ersten Start automatisch ein (PostgreSQL 17, Host-Port 5433 – 5432 ist meist durch eine native PostgreSQL-Installation belegt –, Passwort über LOCAL_PG_PASSWORD).

Nativ: Auf dem Entwicklungsrechner läuft PostgreSQL 17 mit derselben Datenbank. Neu aufbauen bzw. aktualisieren:

$env:SUPABASE_DB_PASSWORD = "<Supabase-DB-Passwort>"
$env:LOCAL_PG_PASSWORD    = "<lokales postgres-Passwort>"
powershell -File scripts\sync-local-db.ps1

Hinweis: Die lokale Kopie dient als Ablage/Backup und für Auswertungen. Logins (Passwort-Hashes, Passkeys) verbleiben ausschließlich in Supabase.

Zugangsdaten

Die Anmeldedaten (Admin, Demo-Mitarbeiter, Datenbank-Passwörter) stehen in ZUGANGSDATEN.md – diese Datei ist bewusst nicht im Repository (.gitignore). Nach dem ersten Login bitte die Startpasswörter ä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%