- TypeScript 97.2%
- CSS 1.5%
- Dockerfile 1.1%
- Shell 0.2%
|
All checks were successful
Docker Build & Push / build (push) Successful in 3m46s
- 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 |
||
|---|---|---|
| .forgejo/workflows | ||
| docker | ||
| docs | ||
| prisma | ||
| public | ||
| src | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .nvmrc | ||
| CHANGELOG.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| next.config.ts | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| README.md | ||
| tsconfig.json | ||
| vitest.config.ts | ||
| vitest.setup.ts | ||
experimenta – Buchungssystem
Ein flexibles Buchungssystem für die experimenta (außerschulischer Lernort). Es deckt zwei sehr unterschiedliche Welten ab:
- 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.
- 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 |
| 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
- In Portainer Stacks → Add stack wählen und den Inhalt von
docker-compose.ymleinfügen (App + PostgreSQL). - Unter Environment variables die nötigen Werte setzen:
IMAGE,POSTGRES_PASSWORD(Pflicht),AUTH_SECRET,ENCRYPTION_KEY,NEXT_PUBLIC_APP_URLsowie optionalPOSTGRES_USER/POSTGRES_DB(Standard jeweilsbooking) und dieSMTP_*-Variablen. Optional außerdem:CRON_SECRET(manueller Erinnerungs-Trigger),SCHEDULER_ENABLED/SCHEDULER_INTERVAL_MIN(interner Scheduler, Standard an/15 min) undRETENTION_DAYS(DSGVO-Aufbewahrung; leer = aus). Schlüssel erzeugen mitopenssl rand -base64 48(AUTH_SECRET) bzw.openssl rand -base64 32(ENCRYPTION_KEY). Das DB-Passwort landet in derDATABASE_URL– daher URL-sicher wählen (keine+ / = @ : ?), am einfachsten peropenssl rand -hex 24. - Beim ersten Deployment einmalig
RUN_SEED=truesetzen, damit Bereiche, Ressourcen und Demo-Logins angelegt werden – danach wieder entfernen. - 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:migrateals neue Migration anlegen und committen; im Container werden sie dann automatisch angewendet.
Demo-Logins (aus dem Seed)
| Rolle | 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_DAYSsowie 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.