# 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. ## 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 - **Reservierungen**: Startzeit + Dauer (15-Minuten-Schritte), Titel, Notiz - **Doppelbuchungsschutz**: atomare Prüfung in serialisierbarer Transaktion (HTTP 409 bei Kollision) - **Ansichten**: Tag / Woche / Monat - **Sicherheit**: zentrale Berechtigungsschicht (`lib/permissions`), Zod-Validierung aller Eingaben, Middleware-Schutz, keine Account-Enumeration ## Tech-Stack Next.js 14 (App Router, TypeScript) · PostgreSQL · Prisma · Tailwind CSS · Docker · Vitest ## Schnellstart mit Docker (empfohlen) ```bash # 1. Session-Secret erzeugen und .env anlegen cp .env.example .env # SESSION_SECRET in .env setzen (mindestens 32 Zeichen): node -e "console.log('SESSION_SECRET=' + require('crypto').randomBytes(48).toString('base64url'))" # 2. Stack starten (PostgreSQL + App) docker compose up --build ``` Die App läuft dann auf http://localhost:3000. Migrationen werden beim Containerstart automatisch angewendet (`prisma migrate deploy`). ## Schnellstart ohne Docker ```bash npm install cp .env.example .env # DATABASE_URL und SESSION_SECRET anpassen npx prisma db push # Schema in die Datenbank schreiben npm run dev ``` Voraussetzung: laufendes PostgreSQL (z. B. nur der `db`-Service aus `docker-compose.yml`: `docker compose up -d db`). ## Erste Schritte 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 ## Projektstruktur ``` app/ Next.js App Router (Seiten + API-Routen) api/auth/ register, login, logout, forgot-password api/calendars/ CRUD + Reservierungen + Invites 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 ```bash npm test ``` Getestet werden insbesondere die **Kollisionslogik** (Überlappungsvarianten aus plan.md Abschnitt 6), Invite-Token-Hashing/-Ablauf, Eingabevalidierung und die komplette Berechtigungsmatrix. ## Sicherheitskonzept (Kurzfassung) | Thema | Umsetzung | | --- | --- | | 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) | | Account-Enumeration | generische Fehler, `/forgot-password` antwortet immer 200 | | Eingaben | Zod-Schemas mit Längen-/Formatgrenzen | ## 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.