No description
  • TypeScript 97.2%
  • CSS 1.5%
  • Dockerfile 1.1%
  • Shell 0.2%
Find a file
Claude bc958c80f8
All checks were successful
Docker Build & Push / build (push) Successful in 3m46s
fix(migrations): Abhaengigkeitsreihenfolge korrigieren (P3018 relation Team does not exist)
- Prisma wendet Migrationen alphabetisch an; coverage_shifts lief vor
  staffing_pools (Team) -> Absturz. Vier unangewendete Migrationen umbenannt:
  20260619_1_staffing_pools, _2_coverage_shifts, _3_zones, _4_shift_team_zone
- Doku: Wiederherstellung fehlgeschlagener Migration in BACKUP_MONITORING.md
2026-06-18 09:26:57 +00:00
.forgejo/workflows perf: Schriften selbst hosten + CI-Cache abschalten 2026-06-17 19:21:29 +00:00
docker fix(docker): vollständige, isolierte Prisma-CLI für Laufzeit-Migrationen 2026-06-17 19:31:31 +00:00
docs fix(migrations): Abhaengigkeitsreihenfolge korrigieren (P3018 relation Team does not exist) 2026-06-18 09:26:57 +00:00
prisma fix(migrations): Abhaengigkeitsreihenfolge korrigieren (P3018 relation Team does not exist) 2026-06-18 09:26:57 +00:00
public fix(docker): public/ via robots.txt anlegen (COPY-Fehler im Runner-Stage) 2026-06-17 19:06:37 +00:00
src feat(zones): Teams zonenscharf einem Schichtblock zuordnen 2026-06-18 09:11:47 +00:00
.dockerignore feat: Docker-Image, Portainer-Stack & Forgejo-CI 2026-06-17 17:43:17 +00:00
.env.example feat(privacy): manuelles Loeschersuchen + Betriebsdoku + README-Update 2026-06-18 06:48:46 +00:00
.gitignore feat: öffentliche Frontpages, Buchungsflow, Team-Bereich & Seed 2026-06-17 17:31:00 +00:00
.nvmrc feat: project foundation – schema, capacity engine, auth, crypto, RBAC 2026-06-17 17:19:45 +00:00
CHANGELOG.md fix(migrations): Abhaengigkeitsreihenfolge korrigieren (P3018 relation Team does not exist) 2026-06-18 09:26:57 +00:00
docker-compose.yml feat(privacy): DSGVO-Aufbewahrung – automatische Anonymisierung alter Gastdaten 2026-06-18 06:39:34 +00:00
Dockerfile feat(admin): vollständiger Backoffice-Bereich (CRUD + RBAC) 2026-06-17 19:56:39 +00:00
next.config.ts feat: app-interner Scheduler fuer Erinnerungen (kein externer Cron noetig) 2026-06-18 06:11:44 +00:00
package-lock.json chore(hardening): Tests, Audit-Log, N+1-Fix, Rate-Limit, Health-Check 2026-06-18 06:33:08 +00:00
package.json fix(migrations): Abhaengigkeitsreihenfolge korrigieren (P3018 relation Team does not exist) 2026-06-18 09:26:57 +00:00
postcss.config.mjs feat: project foundation – schema, capacity engine, auth, crypto, RBAC 2026-06-17 17:19:45 +00:00
README.md feat(privacy): manuelles Loeschersuchen + Betriebsdoku + README-Update 2026-06-18 06:48:46 +00:00
tsconfig.json feat: project foundation – schema, capacity engine, auth, crypto, RBAC 2026-06-17 17:19:45 +00:00
vitest.config.ts chore(hardening): Tests, Audit-Log, N+1-Fix, Rate-Limit, Health-Check 2026-06-18 06:33:08 +00:00
vitest.setup.ts chore(hardening): Tests, Audit-Log, N+1-Fix, Rate-Limit, Health-Check 2026-06-18 06:33:08 +00:00

experimenta Buchungssystem

Ein flexibles Buchungssystem für die experimenta (außerschulischer Lernort). Es deckt zwei sehr unterschiedliche Welten ab:

  1. Ausstellungswelt Ausstellung, Sonderausstellung, Shows und Science Dome (Filmvorführungen). Hier zählt vor allem Raumkapazität, sichtbar gemacht über eine Ampel, die zeigt, wie voll die experimenta in nächster Zeit ist.
  2. Laborkurse für Schulen, Schulklassen und Gruppen. Hier zählt die Personalkapazität: pro Labor sind 1,5 Fachkräfte verfügbar, die je Buchung und Labor flexibel zugeteilt werden.

Das System unterstützt beide Kapazitätsmodelle parallel, bietet pro Bereich eine eigene Frontpage, einen Besucher-Buchungsflow mit E-Mail-Bestätigung sowie einen geschützten Team-Bereich mit Rollensystem. Rechnungen und Online-Zahlung sind architektonisch vorbereitet und können später aktiviert werden.


Funktionsumfang

Öffentlich (Gäste)

  • Markenkonforme Startseite mit Live-Auslastungs-Ampel und Bereichsseiten.
  • Buchungsflow je Bereich: Tagesticket (Ausstellung), Zeitfenster, Sitzplatz oder Personalkapazität (Laborkurse); katalogbasierte Kursbuchung mit Voraussetzungs-Hinweis.
  • Tarife/Ermäßigungen: Mengenauswahl je Tarif mit Live-Summe; der Betrag wird pro Buchung gespeichert.
  • Warteliste bei ausgebuchten Terminen, mit automatischem Nachrücken.
  • Ticket (Druck/PDF) mit QR-Code, Kalender-Einladung (.ics) und E-Mails (Bestätigung, Nachrücken, Erinnerung).
  • Self-Service: Buchung per Referenz + E-Mail aufrufen, umbuchen, stornieren.

Team / Backoffice (rollenbasiert)

  • Belegungsplan (Tag/Woche), Termine inkl. Serienterminen, Kurskatalog.
  • Personal: Qualifikationen, Abwesenheiten und Zuteilungsvorschläge je Termin.
  • Bereichs- und Tarifverwaltung, Buchungen bestätigen/einchecken/stornieren.
  • Check-in am Empfang (QR/Referenz).
  • Audit-Protokoll, DSGVO-Löschersuchen und automatische Aufbewahrung.

Betrieb

  • App-interner Scheduler (Erinnerungen, DSGVO-Aufbewahrung) kein externer Cron nötig.
  • Tests (Vitest), Rate-Limit auf öffentlichen Endpunkten, Health-Check mit DB-Prüfung, automatische DB-Migrationen beim Start.

Tech-Stack

Bereich Technologie
Framework Next.js 15 (App Router) + React 19
Sprache TypeScript
Datenbank PostgreSQL über Prisma ORM
Styling Tailwind CSS v4
Authentifizierung JWT (jose) in HttpOnly-Cookie, bcrypt-Hashes
Verschlüsselung AES-256-GCM für personenbezogene Daten
E-Mail nodemailer (SMTP)
Zahlung Stripe (vorbereitet, später aktivierbar)
Validierung zod
Tests Vitest
Container Docker (Next.js standalone), Forgejo Actions

Schnellstart

1. Voraussetzungen

  • Node.js 20+
  • Eine erreichbare PostgreSQL-Datenbank

2. Abhängigkeiten installieren

npm install

3. Umgebungsvariablen anlegen

cp .env.example .env

Anschließend .env ausfüllen. Schlüssel sicher erzeugen:

# Session-Secret (JWT)
openssl rand -base64 48      # -> AUTH_SECRET

# Verschlüsselungsschlüssel für PII (genau 32 Byte, base64)
openssl rand -base64 32      # -> ENCRYPTION_KEY

DATABASE_URL auf die eigene PostgreSQL-Instanz setzen, SMTP-Daten für E-Mail-Bestätigungen eintragen. Ohne SMTP-Daten läuft alles weiter, es werden nur keine E-Mails versendet (graceful fallback).

4. Datenbank initialisieren

npm run db:generate     # Prisma-Client erzeugen
npm run db:migrate      # Schema in die Datenbank migrieren
npm run db:seed         # Demodaten + Demo-Logins einspielen

5. Entwicklung starten

npm run dev

App läuft auf http://localhost:3000

Produktionsbuild

npm run build && npm start

Deployment mit Docker & Portainer

Das Projekt ist vollständig containerisiert. Bei jedem Push nach main (und bei v*-Tags) baut eine Forgejo-Action das Image und schiebt es in die Forgejo-Container-Registry (.forgejo/workflows/docker.yml). Der Build läuft daemonlos mit Kaniko er benötigt also keinen Docker-Daemon bzw. keinen gemounteten docker.sock auf dem Runner und funktioniert mit einem Standard-Act-Runner, der lediglich Container starten kann.

Image manuell bauen

docker build -t forgejo.thiel.tools/thiel/booking:latest .

Stack in Portainer

  1. In Portainer Stacks → Add stack wählen und den Inhalt von docker-compose.yml einfügen (App + PostgreSQL).
  2. Unter Environment variables die nötigen Werte setzen: IMAGE, POSTGRES_PASSWORD (Pflicht), AUTH_SECRET, ENCRYPTION_KEY, NEXT_PUBLIC_APP_URL sowie optional POSTGRES_USER/POSTGRES_DB (Standard jeweils booking) und die SMTP_*-Variablen. Optional außerdem: CRON_SECRET (manueller Erinnerungs-Trigger), SCHEDULER_ENABLED/SCHEDULER_INTERVAL_MIN (interner Scheduler, Standard an/15 min) und RETENTION_DAYS (DSGVO-Aufbewahrung; leer = aus). Schlüssel erzeugen mit openssl rand -base64 48 (AUTH_SECRET) bzw. openssl rand -base64 32 (ENCRYPTION_KEY). Das DB-Passwort landet in der DATABASE_URL daher URL-sicher wählen (keine + / = @ : ?), am einfachsten per openssl rand -hex 24.
  3. Beim ersten Deployment einmalig RUN_SEED=true setzen, damit Bereiche, Ressourcen und Demo-Logins angelegt werden danach wieder entfernen.
  4. Stack deployen.

Der Container wendet beim Start automatisch alle ausstehenden Datenbankmigrationen an (prisma migrate deploy) und startet anschließend den Server. Der Container-Healthcheck prüft /api/ampel; für externes Monitoring steht /api/health inkl. Datenbank-Prüfung bereit (siehe Backup & Monitoring).

Updates ausrollen

Nach einem neuen Build genügt in Portainer Pull and redeploy (oder ein Stack-Webhook). Das neue Image wird gezogen, die Migrationen laufen automatisch es ist kein manueller Schritt an der Datenbank nötig.

Schemaänderungen während der Entwicklung mit npm run db:migrate als neue Migration anlegen und committen; im Container werden sie dann automatisch angewendet.


Demo-Logins (aus dem Seed)

Rolle E-Mail Passwort
Administrator admin@experimenta.science Admin!Demo2026
Leitung leitung@experimenta.science Manager!Demo2026
Empfang empfang@experimenta.science Empfang!Demo2026

Diese Zugänge dienen nur der Demonstration. Vor einem echten Einsatz unbedingt entfernen bzw. die Passwörter ändern.


Rollen & Rechte

Rolle Kurzbeschreibung
ADMIN Vollzugriff inkl. Benutzerverwaltung
MANAGER Bereiche, Sessions, Ressourcen, Personal & Buchungen
STAFF Eigene Sessions/Buchungen, Personalzuteilung
FRONT_DESK Buchungen anlegen/bearbeiten am Empfang
VIEWER Nur Lesezugriff

Die Rechte-Matrix liegt zentral in src/lib/rbac.ts.


Die Ampel (Kapazitätsanzeige)

Die Ampel ist das Herzstück und wird aus der tatsächlichen Belegung berechnet (src/lib/capacity.ts):

  • 🟢 Grün entspannt (Belegung < 70 %)
  • 🟡 Gelb gut besucht (70 90 %)
  • 🔴 Rot nahezu ausgebucht (> 90 %)

Ohne Datenbankverbindung zeigt die öffentliche Live-Ampel eine neutrale Vorschau, damit die Seite immer funktioniert.


Wichtige Skripte

Befehl Wirkung
npm run dev Entwicklungsserver
npm run build Produktionsbuild (inkl. prisma generate)
npm run start Produktionsserver
npm run typecheck TypeScript-Prüfung ohne Emit
npm run db:migrate Prisma-Migration
npm run db:seed Demodaten einspielen
npm test Unit-Tests (Vitest) einmalig
npm run test:watch Unit-Tests im Watch-Modus

Sicherheit

  • Personenbezogene Daten (E-Mail, Telefon) werden verschlüsselt gespeichert (AES-256-GCM). Für die eindeutige Suche dient ein Blind-Index (HMAC).
  • Passwörter werden mit bcrypt gehasht.
  • Sessions liegen serverseitig (AuthSession) und im HttpOnly-Cookie.
  • RBAC auf allen Team-Aktionen; Audit-Protokoll wichtiger Änderungen.
  • DSGVO: automatische Anonymisierung nach RETENTION_DAYS sowie manuelles Löschersuchen (Team-Bereich → Löschersuchen).
  • Rate-Limit auf öffentlichen Buchungs-/Lookup-Endpunkten.
  • Keine echten Zugangsdaten im Repository siehe .env.example.
  • Sicherheits-Header in next.config.ts.

Mehr Details: docs/ARCHITECTURE.md · Betrieb: docs/BACKUP_MONITORING.md


Stand & Ausblick

Umgesetzt: Kapazitäts-Engine (Raum und Personal) mit Ampel, öffentliche Bereichsseiten und Buchungsflows (Tagesticket, Zeitfenster, Sitzplatz, Kurse), Tarife/Ermäßigungen, Warteliste mit Auto-Nachrücken, Tickets (QR/PDF), .ics-Kalender und E-Mail-Lebenszyklus, Gäste-Self-Service, vollständiges Backoffice (Belegungsplan, Serientermine, Kurskatalog, Personal mit Qualifikationen/Abwesenheiten/Zuteilungsvorschlägen, Bereichs-/Tarifverwaltung, Buchungen, Check-in, Nutzer & Rollen). Dazu Audit-Protokoll, DSGVO (Aufbewahrung + Löschersuchen), app-interner Scheduler, Tests, Rate-Limit und Health-Check.

Geplant: Zwei-Faktor-Authentifizierung, Mehrsprachigkeit (DE/EN) und darauf aufbauend (Betrag/Positionen sind vorbereitet) Online-Zahlung mit Stripe.