7 Commits
Author SHA1 Message Date
florian.wilde 2a2c5affd5 feat: Tage als geschlossen markierbar (Feiertage o. Ä.)
Im Wochenplan lässt sich pro Tag ein optionaler, mehrsprachiger
Schließungsgrund hinterlegen. Geschlossene Tage werden auf der
öffentlichen Speisekarte (Desktop + mobil) und im RSS-Feed als
"Geschlossen" statt mit Essenszeiten angezeigt, im Wochenplan bleibt
"Mahlzeit hinzufügen" für diesen Tag deaktiviert.
2026-07-20 12:35:00 +02:00
florian.wilde cd8b0fab9f feat: Speiseplan-Inhalte variabel mehrsprachig (Teil 2)
Gerichtnamen, Essenszeiten und Allergen-/Zusatzstoff-Bezeichnungen sind
jetzt genauso dynamisch mehrsprachig wie die Bedienoberfläche aus
v1.10.0, statt fest auf Englisch/Polnisch begrenzt zu sein. Neue
Übersetzungstabellen ersetzen die alten name_en/name_pl-Spalten,
DeepL-Auto-Übersetzung und CSV-Import/-Export unterstützen jetzt
beliebig viele aktive Sprachen.
2026-07-20 11:10:06 +02:00
florian.wildeandClaude Sonnet 5 236f8c9490 feat: Dynamisches Sprachpaket-System für die Bedienoberfläche
Teil 1 des "mehrsprachig variabel"-Umbaus (Speiseplan-Inhalte folgen
in einer späteren Version). Sprachen sind jetzt zur Laufzeit über eine
neue Admin-Seite "Sprachen" verwaltbar statt fest im Code zu stehen:

- Neue Tabellen languages/ui_translations (Migration 0004), bestehende
  Englisch-/Polnisch-Texte per generiertem Seed automatisch übernommen
  (Migration 0005) - keine manuelle Nacharbeit, nichts geht verloren.
- lib/i18n/flatten.ts: verschachtelte Dict-Objekte (inkl. Arrays wie
  weekdays.long) <-> flache Schlüssel-Wert-Paare, Basis für Vorlage-
  Export/-Import.
- lib/i18n/template.ts: Vollständigkeits-/Veraltet-Berechnung (Hash
  über die DE-Schlüsselliste) und Import-Validierung (Platzhalter wie
  {message} müssen erhalten bleiben, unbekannte Schlüssel werden
  ignoriert statt Fehler zu werfen).
- getDictionary()/getLocale() (lib/i18n/index.ts) jetzt asynchron und
  DB-gestützt für Nicht-Basis-Sprachen, mit Fallback auf Deutsch für
  fehlende Schlüssel. Locale-Typ bewusst von festem TypeScript-Tupel
  auf string generalisiert (Kompromiss: Compiler prüft Vollständigkeit
  nicht mehr automatisch, das übernimmt jetzt die Veraltet-Erkennung
  zur Laufzeit).
- LanguageSwitcher/LocaleProvider lesen aktive Sprachen jetzt aus dem
  Context statt aus einer festen Konstante; blendet sich bei nur einer
  aktiven Sprache automatisch aus.
- Neue Admin-Routen app/api/admin/languages/* (Liste inkl.
  Vollständigkeit, Anlegen, Aktivieren/Deaktivieren mit
  "mindestens eine muss aktiv bleiben"-Schutz, Löschen außer
  Basis-Sprache, Vorlage-Download, Datei-Import) + LanguageManager.tsx.

Vollständig gegen echten DB-Zustand getestet: Vorlage-Export/-Import
inkl. Platzhalter-Validierung, neue Sprache anlegen, auf eine Sprache
reduzieren (Umschalter verschwindet, "letzte Sprache"-Schutz greift),
Veraltet-Erkennung nach simuliertem neuen Schlüssel, Cascade-Delete.

Version 1.10.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-20 10:34:51 +02:00
florian.wildeandClaude Sonnet 5 de8914f9cc feat: Umschaltbarer Hinweismodus für Allergene/Zusatzstoffe
Neue Migration 0002_labeling_notice.sql: settings.labeling_mode
("detailed"/"notice") + labeling_notice(_en/_pl). Auf der Seite
"Allergene & Zusatzstoffe" (LabelingNoticeEditor) kann zwischen der
detaillierten Auswahl je Gericht und einem editierbaren Hinweistext
umgeschaltet werden. Im Hinweismodus entfällt auf der öffentlichen
Speisekarte (Desktop + Mobile) die Legende und die Kürzel an den
Gerichten, in der Gerichte-Verwaltung wird die Chip-Auswahl beim
Bearbeiten ausgeblendet. Umschalten/Bearbeiten ist für staff und admin
freigegeben, Farben in /api/settings bleiben admin-only.

Version 1.8.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 23:57:43 +02:00
florian.wildeandClaude Sonnet 5 474467811c feat: CSV-Import/-Export für Gerichte/Allergene/Zusatzstoffe, RSS-Fix für mehrere Essenszeiten
CSV-Import/-Export (Gerichte, Allergene, Zusatzstoffe): neue Export-/
Import-Routen unter app/api/*/export und */import, hand-geschriebener
CSV-Parser/-Serializer (lib/csv.ts, keine neue Abhängigkeit), Upsert
über Kürzel (Allergene/Zusatzstoffe) bzw. exakten Namen (Gerichte,
mit Ambiguitäts-Check bei Namensdopplung). UI-Buttons in DishManager
und CodeListManager.

RSS-Feed: <content:encoded> zusätzlich zu <description> ausgeben,
da mehrere Feed-Reader description als kürzbare Vorschau behandeln
und dadurch bei mehreren Essenszeiten pro Tag nur die erste zeigten.

Version 1.7.0.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 23:10:36 +02:00
florian.wilde a9a9cf7ba4 v1.5: Alle Manager-Komponenten auf eigene API-Routen umgestellt
Neue interne Route Handler unter app/api/ (dishes inkl. Bulk-Delete,
sections, allergens, additives, menu-entries, dish-suggestions,
settings, admin/branding) ersetzen den direkten Browser→Postgrest-
Zugriff der bisherigen Supabase-Anbindung. Jede Route prüft Autorisierung
selbst (lib/auth/session.ts requireRole) statt sich auf Postgres-RLS zu
verlassen – ersetzt 1:1 die bisherigen Policies (öffentlich lesbar /
staff+admin schreiben / nur admin für Einstellungen & Benutzer).

Rezeptvorschlag-Dublettencheck (SuggestionForm) läuft jetzt zwingend
serverseitig im POST-Handler, da es keinen Browser→Postgres-Pfad mehr
gibt. "Passkey annehmen" (SuggestionReview) läuft transaktional
(Gericht anlegen + Vorschlag löschen in einem Schritt).

Logo/Favicon: neuer lokaler Dateispeicher (lib/branding.ts, Docker-
Volume) statt Supabase-Storage-Bucket, ausgeliefert über
app/branding/[filename]. logoUrl()/faviconUrl() liefern jetzt einfache
relative Pfade statt einer fest verdrahteten Supabase-Storage-URL.

DeepL-Route (api/translate) und Benutzerverwaltung (api/admin/users)
laufen jetzt gegen die users-Tabelle statt Supabase Auth.

Alle "use client"-Manager-Komponenten (DishManager, WeekEditor,
SectionManager, CodeListManager, SuggestionForm/Review, UserManager,
DesignEditor) rufen ihre jeweiligen /api/-Routen per fetch() statt den
Supabase-Browser-Client zu nutzen.
2026-07-18 11:01:52 +02:00
florian.wilde 28c9ec15c8 v1.5: Selbst gehostete Auth (Passwort + Passkeys), Postgres statt Supabase
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.
2026-07-18 10:42:21 +02:00