Prepare Kalendartool for MPM and disable invites

This commit is contained in:
Kühn
2026-10-08 15:06:22 +02:00
parent bc49c3074e
commit 556202575e
50 changed files with 905 additions and 836 deletions

View File

@@ -1,13 +1,13 @@
# Kalendartool
Einfaches Kalender- und Reservierungstool nach `plan.md`: Ein Admin legt Kalender an und lädt Benutzer per Invite-Link ein. Jeder User sieht **ausschließlich seinen einen Kalender** und kann dort Reservierungen erstellen.
Einfaches Kalender- und Reservierungstool: Ein Admin legt Kalender an und ordnet vorhandene Benutzer direkt zu. Jeder User sieht **ausschließlich seinen einen Kalender** und kann dort Reservierungen erstellen.
## Kernfunktionen (MVP)
- **Auth**: Registrierung, Login, Logout, Passwort vergessen – bcrypt-Hashing, signierte HttpOnly-Session-Cookies, Rate-Limiting beim Login
- **Rollen**: `ADMIN` (0..n Kalender) und `USER` (genau 1 Kalender) – serverseitig erzwungen
- **Kalender**: Titel, Beschreibung, Zeitzone, aktiv/inaktiv
- **Invite-System**: nicht erratbare Tokens (32 Bytes), nur SHA-256-Hash in der DB, Ablaufdatum, Übersicht Aktiv/Abgelaufen/Verwendet
- **Mitglieder**: Administratoren ordnen vorhandene Benutzer direkt einem Kalender zu
- **Reservierungen**: Startzeit + Dauer (15-Minuten-Schritte), Titel, Notiz
- **Doppelbuchungsschutz**: atomare Prüfung in serialisierbarer Transaktion (HTTP 409 bei Kollision)
- **Ansichten**: Tag / Woche / Monat
@@ -17,16 +17,40 @@ Einfaches Kalender- und Reservierungstool nach `plan.md`: Ein Admin legt Kalende
Next.js 14 (App Router, TypeScript) · PostgreSQL · Prisma · Tailwind CSS · Docker · Vitest
## Schnellstart mit Docker (empfohlen)
## MPM Marketplace
Das Repository ist als MPM-Modul vorbereitet. `module.json` beschreibt das
Modul und seine Konfigurationsfelder; `compose.yml` enthält App, PostgreSQL
und tägliche Backups. Die Modul-App veröffentlicht keinen Host-Port. MPM
erreicht sie intern über Port `41030` und die Health-Route `/health`.
Die Anmeldung und Rollen kommen im MPM-Betrieb signiert vom MPM-Gateway.
Die lokale Cookie-Anmeldung bleibt für den eigenständigen Betrieb erhalten.
Kalender und Reservierungen liegen in PostgreSQL. Die benannten Volumes
`pgdata` und `pgbackups` bleiben beim Entfernen des Moduls erhalten.
Bei der ersten MPM-Konfiguration werden zwei getrennte, zufällige Passwörter
für PostgreSQL-Administration und App-Zugriff benötigt. Die Anmeldung läuft
über MPM; lokale Konten und Einladungslinks sind im MPM-Betrieb deaktiviert.
Benutzer und Modulzugriff werden in MPM verwaltet. Öffne das Modul zunächst
einmal mit dem neuen Benutzer, damit sein lokales Profil angelegt wird; ein
Kalenderadministrator kann ihn danach direkt einem Kalender zuordnen. Die
Datenbank-Initialisierung wird nur bei einem leeren Volume ausgeführt. Ein
späteres Ändern der Datenbankpasswörter im MPM-Konfigurationsdialog ändert die
Zugangsdaten eines bestehenden PostgreSQL-Volumes nicht automatisch.
## Schnellstart mit Docker (eigenständiger Betrieb)
```bash
# 1. Session-Secret erzeugen und .env anlegen
cp .env.example .env
# SESSION_SECRET in .env setzen (mindestens 32 Zeichen):
# CALENDAR_ADMIN_PASSWORD, CALENDAR_DB_PASSWORD und SESSION_SECRET in .env
# durch zufällige Werte ersetzen. SESSION_SECRET braucht mindestens 32 Zeichen:
node -e "console.log('SESSION_SECRET=' + require('crypto').randomBytes(48).toString('base64url'))"
# 2. Stack starten (PostgreSQL + App)
docker compose up --build
docker compose -f docker-compose.yml up --build
```
Die App läuft dann auf http://localhost:3000. Migrationen werden beim Containerstart automatisch angewendet (`prisma migrate deploy`).
@@ -35,7 +59,7 @@ Die App läuft dann auf http://localhost:3000. Migrationen werden beim Container
```bash
npm install
cp .env.example .env # DATABASE_URL und SESSION_SECRET anpassen
cp .env.example .env # Passwörter und SESSION_SECRET anpassen
npx prisma db push # Schema in die Datenbank schreiben
npm run dev
```
@@ -46,30 +70,27 @@ Voraussetzung: laufendes PostgreSQL (z. B. nur der `db`-Service aus `docker-comp
1. Erste Registrierung unter `/register` → der **erste Benutzer wird automatisch ADMIN**
2. Im Dashboard einen Kalender anlegen
3. Unter „Benutzer einladen" einen Invite-Link erstellen und verschicken
4. Eingeladene Personen registrieren sich über den Link und sehen sofort nur diesen einen Kalender
3. Im Kalender unter „Mitglieder" vorhandene Benutzer zuordnen
## Projektstruktur
```
app/ Next.js App Router (Seiten + API-Routen)
api/auth/ register, login, logout, forgot-password
api/calendars/ CRUD + Reservierungen + Invites
api/calendars/ CRUD + Reservierungen + Mitglieder
api/reservations/ Bearbeiten/Löschen (atomar)
dashboard/ User: 1 Kalender · Admin: alle Kalender
calendar/[id]/ Ansicht Tag/Woche/Monat
invite/[token]/ Invite-Annahme
admin/ Kalenderverwaltung
components/ UI-, Kalender-, Reservierungs-, Admin-Komponenten
lib/
auth/ Session (JWT-Cookies), Passwort-Hashing
permissions/ zentrale Berechtigungsschicht
reservations/ Konfliktlogik + atomarer Service
invites/ Token-Erzeugung/Hashing
api/ Response-Helfer, Kontext-Lader
validation.ts Zod-Schemas
prisma/schema.prisma Datenmodell
tests/ Unit-Tests (Kollisionen, Invites, Rechte)
tests/ Unit-Tests (Kollisionen, Validierung, Rechte)
```
## Tests
@@ -78,7 +99,7 @@ tests/ Unit-Tests (Kollisionen, Invites, Rechte)
npm test
```
Getestet werden insbesondere die **Kollisionslogik** (Überlappungsvarianten aus plan.md Abschnitt 6), Invite-Token-Hashing/-Ablauf, Eingabevalidierung und die komplette Berechtigungsmatrix.
Getestet werden insbesondere die **Kollisionslogik** (Überlappungsvarianten aus plan.md Abschnitt 6), Eingabevalidierung und die komplette Berechtigungsmatrix.
## Sicherheitskonzept (Kurzfassung)
@@ -86,7 +107,6 @@ Getestet werden insbesondere die **Kollisionslogik** (Überlappungsvarianten aus
| --- | --- |
| Doppelbuchungen | `SERIALIZABLE`-Transaktion in `lib/reservations/service.ts` |
| Berechtigungen | zentrale Funktionen in `lib/permissions/permissions.ts`, geprüft in **jeder** API-Route |
| Invite-Tokens | 32 Byte Zufall, nur SHA-256-Hash in der DB |
| Passwörter | bcrypt (12 Runden), nie im Klartext |
| Sessions | HS256-signierte JWTs in HttpOnly/SameSite-Cookies |
| Login-Missbrauch | Rate-Limiting (5 Versuche / 15 min) |
@@ -95,4 +115,4 @@ Getestet werden insbesondere die **Kollisionslogik** (Überlappungsvarianten aus
## Bewusste MVP-Grenzen (plan.md Abschnitt 18)
Buchungszeitfenster-Regeln, E-Mail-Versand, ICS-Export, wiederkehrende Reservierungen und Statistiken sind bewusst **nicht** Teil von Version 1 – die Struktur (Service-Schicht, Regeln in `lib/`) ist darauf vorbereitet.
Buchungszeitfenster-Regeln, E-Mail-Versand, ICS-Export, wiederkehrende Reservierungen und Statistiken sind bewusst **nicht** Teil von Version 1 – die Struktur (Service-Schicht, Regeln in `lib/`) ist darauf vorbereitet.