v1.5: Docker/Compose/.env.example auf selbst gehostetes Postgres umgestellt
Dockerfile: keine NEXT_PUBLIC_*-Build-Args mehr (Branding-URLs sind jetzt relativ, kein Anon-Key-Konzept mehr – das Image wird dadurch umgebungsunabhängig), db/migrate.mjs + scripts/ werden zusätzlich in die Runner-Stage kopiert (nicht Teil von Next's Standalone-Trace), CMD wendet Migrationen an und legt bei Bedarf den Admin an, bevor der Server startet. docker-compose.yml: db-Dienst ist jetzt die primäre Datenbank (kein Supabase-Dump-Mount mehr), neues Volume branding_data für Logo/Favicon, Bind-Mount für ZUGANGSDATEN.md. .env.example: Supabase-Variablen durch DATABASE_URL + optionales ADMIN_EMAIL ersetzt. package.json: @supabase/ssr und @supabase/supabase-js entfernt. README.md komplett auf die neue Architektur aktualisiert.
This commit is contained in:
+1
-1
@@ -5,7 +5,7 @@ node_modules
|
|||||||
.env*
|
.env*
|
||||||
!.env.example
|
!.env.example
|
||||||
ZUGANGSDATEN.md
|
ZUGANGSDATEN.md
|
||||||
supabase/local
|
data/branding
|
||||||
Dockerfile
|
Dockerfile
|
||||||
docker-compose.yml
|
docker-compose.yml
|
||||||
README.md
|
README.md
|
||||||
|
|||||||
+9
-11
@@ -1,25 +1,23 @@
|
|||||||
# Vorlage für .env.local (Entwicklung) bzw. die Umgebungsvariablen
|
# Vorlage für .env.local (Entwicklung) bzw. die Umgebungsvariablen
|
||||||
# beim Hoster (Live-Betrieb). Echte Werte niemals committen!
|
# beim Hoster (Live-Betrieb). Echte Werte niemals committen!
|
||||||
|
|
||||||
# Supabase-Projekt-URL (Dashboard -> Project Settings -> API)
|
# Verbindung zur (selbst gehosteten) PostgreSQL-Datenbank.
|
||||||
NEXT_PUBLIC_SUPABASE_URL=https://<projekt-ref>.supabase.co
|
DATABASE_URL=postgres://postgres:<passwort>@localhost:5432/canteen_menu
|
||||||
|
|
||||||
# Öffentlicher anon-Key (darf im Browser landen, RLS schützt die Daten)
|
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon-key>
|
|
||||||
|
|
||||||
# Service-Role-Key (GEHEIM! Nur serverseitig, für die Benutzerverwaltung)
|
|
||||||
SUPABASE_SERVICE_ROLE_KEY=<service-role-key>
|
|
||||||
|
|
||||||
# Öffentliche URL, unter der die App erreichbar ist (Schema + Domain).
|
# Öffentliche URL, unter der die App erreichbar ist (Schema + Domain).
|
||||||
# Wird für den CSRF-Origin-Check hinter Reverse-Proxies genutzt und ist
|
# Wird für den CSRF-Origin-Check hinter Reverse-Proxies genutzt und ist
|
||||||
# zugleich die Passkey-Domain: nach einer Änderung einmalig
|
# zugleich die Passkey-Domain (RP-ID/Origin werden direkt daraus
|
||||||
# SUPABASE_ACCESS_TOKEN=<token> node scripts/configure-passkeys.mjs
|
# abgeleitet, kein separater Konfigurationsschritt nötig).
|
||||||
# ausführen, damit Passkeys auf der neuen Domain funktionieren.
|
|
||||||
APP_URL=http://localhost:3000
|
APP_URL=http://localhost:3000
|
||||||
|
|
||||||
# Titel im Browser-Tab und Überschrift, solange kein Logo hochgeladen ist.
|
# Titel im Browser-Tab und Überschrift, solange kein Logo hochgeladen ist.
|
||||||
SITE_TITLE=Canteen Menu
|
SITE_TITLE=Canteen Menu
|
||||||
|
|
||||||
|
# Optional: E-Mail-Adresse für den automatisch angelegten Admin-Erstnutzer
|
||||||
|
# (nur relevant, solange die users-Tabelle noch leer ist). Ohne Angabe
|
||||||
|
# wird admin@<Hostname aus APP_URL> verwendet.
|
||||||
|
ADMIN_EMAIL=
|
||||||
|
|
||||||
# Optional: DeepL-API-Key für automatische Übersetzung von Gerichten nach
|
# Optional: DeepL-API-Key für automatische Übersetzung von Gerichten nach
|
||||||
# Englisch/Polnisch (kostenloser Free-Tier unter deepl.com/de/pro-api,
|
# Englisch/Polnisch (kostenloser Free-Tier unter deepl.com/de/pro-api,
|
||||||
# Free-Keys enden auf ":fx"). Ohne Key bleibt "Automatisch übersetzen"
|
# Free-Keys enden auf ":fx"). Ohne Key bleibt "Automatisch übersetzen"
|
||||||
|
|||||||
+7
-7
@@ -15,12 +15,6 @@ WORKDIR /app
|
|||||||
COPY --from=deps /app/node_modules ./node_modules
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# NEXT_PUBLIC_*-Variablen werden zur Build-Zeit ins Client-Bundle eingebacken
|
|
||||||
ARG NEXT_PUBLIC_SUPABASE_URL
|
|
||||||
ARG NEXT_PUBLIC_SUPABASE_ANON_KEY
|
|
||||||
ENV NEXT_PUBLIC_SUPABASE_URL=$NEXT_PUBLIC_SUPABASE_URL
|
|
||||||
ENV NEXT_PUBLIC_SUPABASE_ANON_KEY=$NEXT_PUBLIC_SUPABASE_ANON_KEY
|
|
||||||
|
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# ---------- Runtime ----------
|
# ---------- Runtime ----------
|
||||||
@@ -35,7 +29,13 @@ RUN addgroup -S nodejs && adduser -S nextjs -G nodejs
|
|||||||
COPY --from=builder /app/public ./public
|
COPY --from=builder /app/public ./public
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
# Von Next's Standalone-Output nicht automatisch mitgetraced (werden nicht
|
||||||
|
# von App-Routen importiert, sondern eigenständig vor dem Start ausgeführt).
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/db ./db
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/scripts ./scripts
|
||||||
|
|
||||||
|
RUN mkdir -p /data/branding && chown nextjs:nodejs /data/branding
|
||||||
|
|
||||||
USER nextjs
|
USER nextjs
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD ["node", "server.js"]
|
CMD ["sh", "-c", "node db/migrate.mjs && node scripts/bootstrap-admin.mjs && exec node server.js"]
|
||||||
|
|||||||
@@ -4,8 +4,11 @@ Digitale Speisekarte für eine Firmenkantine (ca. 250 Mitarbeiter) mit
|
|||||||
öffentlicher Wochenansicht, Kantinen-Bereich zur Pflege der Gerichte und
|
öffentlicher Wochenansicht, Kantinen-Bereich zur Pflege der Gerichte und
|
||||||
Admin-Bereich für Benutzerverwaltung und Branding.
|
Admin-Bereich für Benutzerverwaltung und Branding.
|
||||||
|
|
||||||
**Stack:** Next.js 16 (App Router, TypeScript, Tailwind CSS) · Supabase
|
**Stack:** Next.js 16 (App Router, TypeScript, Tailwind CSS) · selbst
|
||||||
(PostgreSQL, Auth inkl. Passkeys, Storage, Row Level Security)
|
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
|
Änderungen je Version stehen in [CHANGELOG.md](CHANGELOG.md), Git-Tags
|
||||||
markieren die jeweiligen Versionsstände (`git tag`).
|
markieren die jeweiligen Versionsstände (`git tag`).
|
||||||
@@ -14,14 +17,14 @@ markieren die jeweiligen Versionsstände (`git tag`).
|
|||||||
|
|
||||||
| Bereich | Funktionen |
|
| Bereich | Funktionen |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| Öffentlich (ohne Login) | Wochenplan Mo–Fr mit den Essenszeiten Brotzeit (09:00–10:00), Mittagessen, Mittagessen vegetarisch und Suppe (je 11:30–13:30); 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 |
|
| Ö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`) | 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) |
|
| 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 |
|
| 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**
|
Anmeldung mit E-Mail + Passwort oder alternativ per **Passkey**
|
||||||
(Registrierung im eigenen Konto nach dem ersten Login). Die
|
(Registrierung im eigenen Konto nach dem ersten Login). Die
|
||||||
Passwort-Anmeldung kann pro Konto deaktiviert werden, sobald mindestens
|
Passwort-Anmeldung kann pro Konto deaktiviert werden, sobald mindestens
|
||||||
ein Passkey registriert ist.
|
ein Passkey registriert ist (serverseitig erzwungen).
|
||||||
|
|
||||||
Die gesamte Oberfläche ist auf Deutsch, Englisch und Polnisch verfügbar
|
Die gesamte Oberfläche ist auf Deutsch, Englisch und Polnisch verfügbar
|
||||||
(Sprachumschalter, Cookie-basiert). Gerichte, Essenszeiten, Allergene und
|
(Sprachumschalter, Cookie-basiert). Gerichte, Essenszeiten, Allergene und
|
||||||
@@ -39,18 +42,29 @@ app/ Next.js-Routen
|
|||||||
intern/ Kantinen-Bereich (Rolle: staff oder admin)
|
intern/ Kantinen-Bereich (Rolle: staff oder admin)
|
||||||
vorschlaege/ Rezeptvorschläge sichten/übernehmen
|
vorschlaege/ Rezeptvorschläge sichten/übernehmen
|
||||||
admin/ Verwaltung (Rolle: admin)
|
admin/ Verwaltung (Rolle: admin)
|
||||||
api/admin/users/ Benutzerverwaltung (serverseitig, Service-Role-Key)
|
branding/[filename]/ Logo/Favicon ausliefern (öffentlich)
|
||||||
api/translate/ DeepL-Anbindung (serverseitig)
|
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
|
api/locale/ Sprach-Cookie setzen
|
||||||
components/ Client-Komponenten (WeekEditor, DishManager, …)
|
components/ Client-Komponenten (WeekEditor, DishManager, …)
|
||||||
components/i18n/ Sprachkontext, Sprachumschalter
|
components/i18n/ Sprachkontext, Sprachumschalter
|
||||||
lib/i18n/ Wörterbücher (de/en/pl), Locale-Helfer
|
lib/i18n/ Wörterbücher (de/en/pl), Locale-Helfer
|
||||||
lib/supabase/ Supabase-Clients (Browser / Server / Admin)
|
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
|
lib/ Typen, Datums-/Formatierungshelfer
|
||||||
proxy.ts Session-Refresh + Schutz von /intern und /admin
|
proxy.ts Session-Prüfung + Schutz von /intern, /admin, /konto
|
||||||
supabase/migrations/ Schema & Seed-Daten (Referenz/Doku)
|
db/migrations/ Datenbankschema (reines SQL, nummeriert)
|
||||||
supabase/local/ Skripte + Dumps für die lokale PostgreSQL-Kopie
|
db/migrate.mjs Wendet neue Migrationen an (idempotent, bei jedem Start)
|
||||||
scripts/ Hilfsskripte (DB-Migration, lokaler Sync)
|
scripts/bootstrap-admin.mjs
|
||||||
|
Legt bei leerer Datenbank automatisch einen
|
||||||
|
Admin mit Zufallspasswort an (siehe ZUGANGSDATEN.md)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Entwicklung
|
## Entwicklung
|
||||||
@@ -66,25 +80,25 @@ docker compose --env-file .env.local --profile dev up dev
|
|||||||
# http://localhost:3000 (Hot-Reload, Quellcode wird gemountet)
|
# http://localhost:3000 (Hot-Reload, Quellcode wird gemountet)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Variante B – lokal installiertes Node.js:**
|
**Variante B – lokal installiertes Node.js + PostgreSQL:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
npm run dev # http://localhost:3000 (bindet an 0.0.0.0, auch im LAN erreichbar)
|
npm run dev # http://localhost:3000 (bindet an 0.0.0.0, auch im LAN erreichbar)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Umgebungsvariablen (Live-Betrieb)
|
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)).
|
||||||
|
|
||||||
Beim Webserver/Hoster müssen genau diese drei Variablen hinterlegt werden
|
## Umgebungsvariablen (Live-Betrieb)
|
||||||
(Werte: Supabase-Dashboard → Project Settings → API Keys):
|
|
||||||
|
|
||||||
| Variable | Bedeutung | Geheim? |
|
| Variable | Bedeutung | Geheim? |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `NEXT_PUBLIC_SUPABASE_URL` | URL des Supabase-Projekts, z. B. `https://hvzikwjoyhoakedlywhj.supabase.co` | nein |
|
| `DATABASE_URL` | Verbindung zur PostgreSQL-Datenbank, z. B. `postgres://postgres:<passwort>@db:5432/canteen_menu` | **ja** |
|
||||||
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | Öffentlicher anon-Key (RLS schützt die Daten) | nein |
|
| `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 |
|
||||||
| `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 |
|
|
||||||
| `SITE_TITLE` | Titel im Browser-Tab und Überschrift ohne Logo, Default `Canteen Menu` | 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!** |
|
| `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
|
Build & Start auf dem Server – **mit Docker** (empfohlen, Node.js und
|
||||||
@@ -93,82 +107,55 @@ PostgreSQL müssen nicht installiert sein):
|
|||||||
```bash
|
```bash
|
||||||
docker compose --env-file .env.local up -d --build
|
docker compose --env-file .env.local up -d --build
|
||||||
# App: Port 3000 (hinter Reverse-Proxy mit HTTPS betreiben)
|
# App: Port 3000 (hinter Reverse-Proxy mit HTTPS betreiben)
|
||||||
# Lokale DB-Kopie: Port 5433 (Dienst "db", optional)
|
# PostgreSQL: Port 5433
|
||||||
```
|
```
|
||||||
|
|
||||||
Oder klassisch ohne Docker:
|
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
|
```bash
|
||||||
npm ci
|
npm ci
|
||||||
npm run build
|
npm run build
|
||||||
npm run start # Port 3000, bindet an 0.0.0.0 (hinter Reverse-Proxy betreiben)
|
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 (RP-ID = Hostname aus `APP_URL`).
|
||||||
|
Vor dem Livegang `APP_URL` auf die öffentliche URL setzen – ein
|
||||||
Passkeys sind an die Domain gebunden. Aktuell ist `localhost` konfiguriert.
|
zusätzlicher Konfigurationsschritt ist nicht nötig. Bereits registrierte
|
||||||
Vor dem Livegang: `APP_URL` auf die öffentliche URL setzen und einmalig
|
Passkeys einer vorherigen Domain verlieren dabei ihre Gültigkeit und
|
||||||
|
müssen neu registriert werden.
|
||||||
```bash
|
|
||||||
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:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
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
|
## Datenbank
|
||||||
|
|
||||||
### Supabase (führende Datenbank)
|
Reines PostgreSQL (kein Supabase, keine Row-Level-Security-Policies) –
|
||||||
|
alle Zugriffsrechte werden in den Route-Handlern geprüft (`lib/auth/session.ts`,
|
||||||
|
`requireRole`).
|
||||||
|
|
||||||
- Projekt-Ref: `hvzikwjoyhoakedlywhj` (Region `eu-central-1`, Frankfurt)
|
- Schema: `db/migrations/*.sql`, nummeriert, wird von `db/migrate.mjs`
|
||||||
- Schema und Seed-Daten: `supabase/migrations/0001_init.sql` und `0002_seed.sql`
|
automatisch angewendet (idempotent, bei jedem Start des Servers bzw.
|
||||||
- Ausführen weiterer SQL-Dateien:
|
Docker-Containers).
|
||||||
`SUPABASE_ACCESS_TOKEN=<token> node scripts/db-apply.mjs <datei.sql>`
|
- Neue Migration hinzufügen: `db/migrations/000N_beschreibung.sql`
|
||||||
|
anlegen, wird beim nächsten Start automatisch eingespielt.
|
||||||
|
- Manuell migrieren: `npm run db:migrate`
|
||||||
|
|
||||||
Sicherheitsmodell (Row Level Security):
|
Rechte-Modell (in Anwendungscode statt Datenbank-RLS):
|
||||||
|
|
||||||
- `anon` darf ausschließlich lesen (Speisekarte, Essenszeiten, Allergene,
|
- Öffentlich lesbar (kein Login nötig): Speisekarte, Essenszeiten,
|
||||||
Zusatzstoffe, Einstellungen).
|
Allergene, Zusatzstoffe, Logo/Favicon.
|
||||||
- `staff`/`admin` (Rolle im `profiles`-Eintrag) pflegen Gerichte,
|
- `staff`/`admin` pflegen Gerichte, Essenszeiten, Wochenplan und
|
||||||
Wochenplan und Kennzeichnungen.
|
Kennzeichnungen.
|
||||||
- Nur `admin` ändert Einstellungen (Farben/Logo) und verwaltet Benutzer;
|
- Nur `admin` ändert Einstellungen (Farben/Logo) und verwaltet Benutzer.
|
||||||
das Anlegen/Löschen von Benutzern läuft serverseitig über den
|
- Logo/Favicon liegen als Dateien in `BRANDING_DIR` (Docker-Volume
|
||||||
Service-Role-Key (`app/api/admin/users`).
|
`branding_data`), öffentlich ausgeliefert über `/branding/[filename]`.
|
||||||
- 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:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
$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
|
## Zugangsdaten
|
||||||
|
|
||||||
Die Anmeldedaten (Admin, Demo-Mitarbeiter, Datenbank-Passwörter) stehen in
|
Beim allerersten Start mit leerer Datenbank legt `scripts/bootstrap-admin.mjs`
|
||||||
`ZUGANGSDATEN.md` – diese Datei ist bewusst **nicht** im Repository
|
automatisch einen Admin-Benutzer mit zufällig generiertem Passwort an und
|
||||||
(`.gitignore`). Nach dem ersten Login bitte die Startpasswörter ändern.
|
schreibt die Zugangsdaten nach `ZUGANGSDATEN.md` – diese Datei ist bewusst
|
||||||
|
**nicht** im Repository (`.gitignore`). Nach dem ersten Login bitte das
|
||||||
|
Startpasswort ändern.
|
||||||
|
|||||||
+19
-20
@@ -1,15 +1,12 @@
|
|||||||
# =============================================================
|
# =============================================================
|
||||||
# Canteen Menu – Docker-Setup
|
# Canteen Menu – Docker-Setup
|
||||||
#
|
#
|
||||||
# Produktion (App + lokale PostgreSQL-Kopie):
|
# Produktion (App + PostgreSQL):
|
||||||
# docker compose --env-file .env.local up -d --build
|
# docker compose --env-file .env.local up -d --build
|
||||||
#
|
#
|
||||||
# Entwicklung (Dev-Server mit Hot-Reload, kein lokales Node nötig):
|
# Entwicklung (Dev-Server mit Hot-Reload, kein lokales Node nötig):
|
||||||
# docker compose --env-file .env.local --profile dev up dev
|
# docker compose --env-file .env.local --profile dev up dev
|
||||||
#
|
#
|
||||||
# Die --env-file-Angabe speist NEXT_PUBLIC_* in den Build und
|
|
||||||
# SUPABASE_SERVICE_ROLE_KEY in die Laufzeit-Umgebung.
|
|
||||||
#
|
|
||||||
# "name" fixiert den Compose-Projektnamen explizit auf "canteen-menu" –
|
# "name" fixiert den Compose-Projektnamen explizit auf "canteen-menu" –
|
||||||
# ohne diese Angabe leitet Compose ihn aus dem Ordnernamen ab.
|
# ohne diese Angabe leitet Compose ihn aus dem Ordnernamen ab.
|
||||||
# =============================================================
|
# =============================================================
|
||||||
@@ -21,32 +18,32 @@ services:
|
|||||||
app:
|
app:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
args:
|
|
||||||
NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL}
|
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY}
|
|
||||||
image: canteen-menu-app
|
image: canteen-menu-app
|
||||||
container_name: canteen-menu-app
|
container_name: canteen-menu-app
|
||||||
environment:
|
environment:
|
||||||
NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL}
|
DATABASE_URL: postgres://postgres:${LOCAL_PG_PASSWORD:-canteen_menu}@db:5432/canteen_menu
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY}
|
|
||||||
SUPABASE_SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY}
|
|
||||||
# Öffentliche URL der App (Domain hinter dem Reverse-Proxy).
|
# Öffentliche URL der App (Domain hinter dem Reverse-Proxy).
|
||||||
# Wird für den CSRF-Origin-Check genutzt; dieselbe URL ist die
|
# Wird für den CSRF-Origin-Check genutzt; dieselbe URL ist auch die
|
||||||
# Passkey-Domain (siehe scripts/configure-passkeys.mjs).
|
# Passkey-Domain (RP-ID/Origin werden direkt daraus abgeleitet).
|
||||||
APP_URL: ${APP_URL:-http://localhost:3000}
|
APP_URL: ${APP_URL:-http://localhost:3000}
|
||||||
# Titel im Browser-Tab und Überschrift, solange kein Logo hochgeladen ist.
|
# Titel im Browser-Tab und Überschrift, solange kein Logo hochgeladen ist.
|
||||||
SITE_TITLE: ${SITE_TITLE:-Canteen Menu}
|
SITE_TITLE: ${SITE_TITLE:-Canteen Menu}
|
||||||
|
# Optional: E-Mail des automatisch angelegten Admin-Erstnutzers.
|
||||||
|
ADMIN_EMAIL: ${ADMIN_EMAIL:-}
|
||||||
# Optional: DeepL-API-Key für automatische Übersetzung von Gerichten
|
# Optional: DeepL-API-Key für automatische Übersetzung von Gerichten
|
||||||
# (Kürzel-Erklärungen, Bereichen) nach Englisch/Polnisch. Ohne Key
|
# (Essenszeiten, Kürzel-Erklärungen) nach Englisch/Polnisch. Ohne Key
|
||||||
# bleibt "Automatisch übersetzen" inaktiv, manuelle Eingabe geht immer.
|
# bleibt "Automatisch übersetzen" inaktiv, manuelle Eingabe geht immer.
|
||||||
DEEPL_API_KEY: ${DEEPL_API_KEY:-}
|
DEEPL_API_KEY: ${DEEPL_API_KEY:-}
|
||||||
ports:
|
ports:
|
||||||
- "0.0.0.0:3000:3000"
|
- "0.0.0.0:3000:3000"
|
||||||
|
volumes:
|
||||||
|
- branding_data:/data/branding
|
||||||
|
- ./ZUGANGSDATEN.md:/app/ZUGANGSDATEN.md
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# ---------- Lokale PostgreSQL-Kopie (Ablage/Auswertungen) ----------
|
# ---------- PostgreSQL (primäre Datenbank) ----------
|
||||||
# Beim ersten Start werden die Skripte aus supabase/local
|
|
||||||
# automatisch in alphabetischer Reihenfolge eingespielt.
|
|
||||||
db:
|
db:
|
||||||
image: postgres:17-alpine
|
image: postgres:17-alpine
|
||||||
container_name: canteen-menu-db
|
container_name: canteen-menu-db
|
||||||
@@ -59,7 +56,6 @@ services:
|
|||||||
- "5433:5432"
|
- "5433:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- pgdata:/var/lib/postgresql/data
|
- pgdata:/var/lib/postgresql/data
|
||||||
- ./supabase/local:/docker-entrypoint-initdb.d:ro
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
# ---------- Dev-Server (nur mit --profile dev) ----------
|
# ---------- Dev-Server (nur mit --profile dev) ----------
|
||||||
@@ -70,18 +66,21 @@ services:
|
|||||||
working_dir: /app
|
working_dir: /app
|
||||||
command: sh -c "npm install && npm run dev"
|
command: sh -c "npm install && npm run dev"
|
||||||
environment:
|
environment:
|
||||||
NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL}
|
DATABASE_URL: postgres://postgres:${LOCAL_PG_PASSWORD:-canteen_menu}@db:5432/canteen_menu
|
||||||
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY}
|
|
||||||
SUPABASE_SERVICE_ROLE_KEY: ${SUPABASE_SERVICE_ROLE_KEY}
|
|
||||||
APP_URL: ${APP_URL:-http://localhost:3000}
|
APP_URL: ${APP_URL:-http://localhost:3000}
|
||||||
SITE_TITLE: ${SITE_TITLE:-Canteen Menu}
|
SITE_TITLE: ${SITE_TITLE:-Canteen Menu}
|
||||||
|
ADMIN_EMAIL: ${ADMIN_EMAIL:-}
|
||||||
DEEPL_API_KEY: ${DEEPL_API_KEY:-}
|
DEEPL_API_KEY: ${DEEPL_API_KEY:-}
|
||||||
ports:
|
ports:
|
||||||
- "0.0.0.0:3000:3000"
|
- "0.0.0.0:3000:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
- dev_node_modules:/app/node_modules
|
- dev_node_modules:/app/node_modules
|
||||||
|
- branding_data:/app/data/branding
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
dev_node_modules:
|
dev_node_modules:
|
||||||
|
branding_data:
|
||||||
|
|||||||
Generated
-120
@@ -10,8 +10,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@simplewebauthn/browser": "^13.3.0",
|
"@simplewebauthn/browser": "^13.3.0",
|
||||||
"@simplewebauthn/server": "^13.3.2",
|
"@simplewebauthn/server": "^13.3.2",
|
||||||
"@supabase/ssr": "^0.12.3",
|
|
||||||
"@supabase/supabase-js": "^2.110.7",
|
|
||||||
"next": "16.2.10",
|
"next": "16.2.10",
|
||||||
"pg": "^8.22.0",
|
"pg": "^8.22.0",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
@@ -1525,102 +1523,6 @@
|
|||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@supabase/auth-js": {
|
|
||||||
"version": "2.110.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.110.7.tgz",
|
|
||||||
"integrity": "sha512-M5Bpl4hCv6kHcOO/xM06Dyfg1mYLHljMkp1plhzG9IRZPc3czvyMsSN1XpL5+GKisOKM3lSN59zhpcm6sMVXfA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "2.8.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=22.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@supabase/functions-js": {
|
|
||||||
"version": "2.110.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.110.7.tgz",
|
|
||||||
"integrity": "sha512-megYmexlYEoR/0qlsr4Snh9wtzAodO7MAri3NMevZrXzNvQRKlvmTcSBoKGLQEPDakgDZMqbMdf9DwoZz6qfoA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "2.8.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=22.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@supabase/phoenix": {
|
|
||||||
"version": "0.4.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.5.tgz",
|
|
||||||
"integrity": "sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/@supabase/postgrest-js": {
|
|
||||||
"version": "2.110.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.110.7.tgz",
|
|
||||||
"integrity": "sha512-ban6YV0djhVaqVYezlOARKLIuOBSvLLhyQVZjA2nxPrtswhxHCl1+gI4giFgI9ATQAaMNbUZb4JXiuL5lEA/5g==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"tslib": "2.8.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=22.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@supabase/realtime-js": {
|
|
||||||
"version": "2.110.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.110.7.tgz",
|
|
||||||
"integrity": "sha512-AMtZjyFA2gsmjuxopPNS/sRznLQHG0Ht5x+ytTPTOh3vAcOTUlVRLx7gW4/CONNnbb3PKOkE+HmM35HOSbmomQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@supabase/phoenix": "0.4.5",
|
|
||||||
"tslib": "2.8.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=22.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@supabase/ssr": {
|
|
||||||
"version": "0.12.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.12.3.tgz",
|
|
||||||
"integrity": "sha512-qWXJ/dI7CiYDKyTgIPqJ4Qkh8y5edh2LdF/nkN48z47mmEVzAO2OE+YErYeQ/UemVfonn/F4kFz+lhLqoVMdpw==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"cookie": "^1.0.2"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@supabase/supabase-js": "^2.110.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@supabase/storage-js": {
|
|
||||||
"version": "2.110.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.110.7.tgz",
|
|
||||||
"integrity": "sha512-2tcDE8cjEDy1uKxKavBpKQod1JdMV1jDXQag48TCa+kycmJOltc0yVabC0BUlhOwAl6WykXU2aOsH3ELMtZrmQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"iceberg-js": "^0.8.1",
|
|
||||||
"tslib": "2.8.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=22.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@supabase/supabase-js": {
|
|
||||||
"version": "2.110.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.110.7.tgz",
|
|
||||||
"integrity": "sha512-AnfO3A230Shy6RMO7cya3Wl1OcXnABJrzH8vP+fY7/RFjhzcchB7DjKkkTIAntlwekD+GkSFzEvt2tC+D4Fp8w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"@supabase/auth-js": "2.110.7",
|
|
||||||
"@supabase/functions-js": "2.110.7",
|
|
||||||
"@supabase/postgrest-js": "2.110.7",
|
|
||||||
"@supabase/realtime-js": "2.110.7",
|
|
||||||
"@supabase/storage-js": "2.110.7"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=22.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@swc/helpers": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.15",
|
"version": "0.5.15",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||||
@@ -3150,19 +3052,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cookie": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=18"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/express"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
@@ -4450,15 +4339,6 @@
|
|||||||
"hermes-estree": "0.25.1"
|
"hermes-estree": "0.25.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/iceberg-js": {
|
|
||||||
"version": "0.8.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
|
|
||||||
"integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=20.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/ignore": {
|
"node_modules/ignore": {
|
||||||
"version": "5.3.2",
|
"version": "5.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@simplewebauthn/browser": "^13.3.0",
|
"@simplewebauthn/browser": "^13.3.0",
|
||||||
"@simplewebauthn/server": "^13.3.2",
|
"@simplewebauthn/server": "^13.3.2",
|
||||||
"@supabase/ssr": "^0.12.3",
|
|
||||||
"@supabase/supabase-js": "^2.110.7",
|
|
||||||
"next": "16.2.10",
|
"next": "16.2.10",
|
||||||
"pg": "^8.22.0",
|
"pg": "^8.22.0",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
|
|||||||
Reference in New Issue
Block a user