# Vereinskalender State-of-the-art Kalenderapp für Vereine mit Admin-Freigaben, persönlichen Kalenderansichten und iCal-Export. Die App basiert auf Next.js (App Router), Prisma und NextAuth (Credentials). ## Features - Admins können Termine sofort freigeben oder Vorschläge bestätigen/ablehnen. - Mitglieder schlagen Termine vor; Freigaben laufen über das Admin-Panel. - Neue Registrierungen müssen durch Admins freigeschaltet werden. - E-Mail-Verifizierung kann durch Superadmins optional deaktiviert werden. - Mehrere Kalenderansichten (Monat, Woche, Liste) mit FullCalendar. - Eine Standardansicht pro Benutzer mit iCal-Abonnement für externe Apps (iOS/Android). - Kategorien zur Strukturierung (nur Admins dürfen Kategorien anlegen). - Kategorien können abonniert werden, damit neue und bestehende Termine automatisch in die Ansicht fallen. ## Tech-Stack - Frontend: Next.js 14 (App Router), React 18, Tailwind CSS, FullCalendar (v6.1.20 CSS lokal) - Backend: Next.js Route Handlers, Prisma ORM, SQLite - Auth: NextAuth (Credentials + Prisma Adapter) - Export: iCal via `ical-generator` - Import: iCal via `node-ical` ## Projektstruktur - `app/` - Routen, Layouts und Seiten - `components/` - UI-Komponenten (Kalender, Admin-Panel, View-Manager) - `app/api/` - API-Routen (Auth, Events, Views, iCal) - `prisma/` - Schema und Migrations - `lib/` - Prisma Client, Auth-Helfer ## Lokale Entwicklung ```bash npm install cp .env.example .env npm run prisma:migrate npm run dev ``` Öffne `http://localhost:3000`. ## Konfiguration In `.env` (lokal) bzw. per Umgebungsvariablen (Docker/Prod): ``` DATABASE_URL="file:./data/dev.db" NEXTAUTH_SECRET="replace-with-strong-secret" NEXTAUTH_URL="http://localhost:3101" ADMIN_EMAILS="admin@example.com" SUPERADMIN_EMAILS="superadmin@example.com" SMTP_HOST="smtp.example.com" SMTP_PORT="587" SMTP_USER="user@example.com" SMTP_PASS="password" SMTP_SECURE="false" SMTP_FROM="Vereinskalender " NOMINATIM_USER_AGENT="vereinskalender/1.0 (mailto:admin@example.com)" RATE_LIMIT_WINDOW_MINUTES="15" RATE_LIMIT_LOGIN="10" RATE_LIMIT_REGISTER="5" RATE_LIMIT_PASSWORD_RESET="3" RATE_LIMIT_VERIFY_EMAIL="3" RATE_LIMIT_ICAL_IMPORT="5" ``` ## Admin-Setup `ADMIN_EMAILS` (kommagetrennt) steuert, welche Accounts beim Signup als Admin markiert werden. Admin-Konten werden sofort freigeschaltet, normale Mitglieder bleiben auf `PENDING`. `SUPERADMIN_EMAILS` (kommagetrennt) steuert Superadmins, die System-Einstellungen verwalten dürfen. ## Wichtige Befehle - `npm run dev` - lokale Entwicklung - `npm run build` - Production Build - `npm run start` - Server starten (Production) - `npm run prisma:migrate` - DB Migrationen für SQLite (Dev) - `npm run prisma:deploy` - Schema push (Container-Start) - `npm run prisma:studio` - Prisma Studio ## APIs (kurz) - `POST /api/register` - Registrierung - `GET /api/events` - Events (Admins sehen alles, User nur eigene + freigegebene) - `POST /api/events` - Termin vorschlagen/anlegen - `PATCH /api/events/:id` - Freigeben/Ablehnen (Admin) - `GET /api/categories` - Kategorien anzeigen - `POST /api/categories` - Kategorie anlegen (Admin) - `GET /api/users?status=PENDING` - Offene Registrierungen (Admin) - `PATCH /api/users` - Benutzer freischalten/ändern (Admin) - `DELETE /api/users?id=...` - Benutzer deaktivieren (Admin) - `DELETE /api/users?id=...&hard=true` - Benutzer endgültig löschen (Superadmin) - `GET /api/views` - Eigene Ansichten - `POST /api/views` - Ansicht erstellen - `POST /api/views/:id/items` - Termin zur Ansicht - `DELETE /api/views/:id/items` - Termin entfernen - `GET /api/views/default` - Standardansicht laden/erstellen - `GET /api/ical/:token` - iCal Feed der Ansicht - `POST /api/ical/import` - iCal-Datei importieren (Admin/Superadmin) - `POST /api/views/default/rotate` - iCal-Link erneuern - `POST /api/views/:id/categories` - Kategorie abonnieren - `DELETE /api/views/:id/categories` - Kategorie-Abo entfernen - `PATCH /api/profile` - E-Mail/Passwort aktualisieren - `POST /api/password-reset/request` - Passwort-Reset Link anfordern - `POST /api/password-reset/confirm` - Passwort setzen (mit Token) - `POST /api/verify-email/request` - Verifizierungslink senden - `POST /api/verify-email/confirm` - E-Mail verifizieren - `GET /api/settings/system` - Ortsanbieter + API Key abrufen (Login) - `POST /api/settings/system` - Ortsanbieter/Key speichern (Superadmin) - `GET /api/places/autocomplete` - Places Autocomplete (Login) - `GET /api/places/details` - Places Details (Login) - `GET /api/places/reverse` - Reverse-Geocoding (Login) - `GET /api/settings/app-name` - App-Name abrufen - `POST /api/settings/app-name` - App-Name setzen (Superadmin) ## iCal-Abonnement Unter `/settings` wird die iCal-URL angezeigt. Diese kann in Kalender-Apps (iOS/Android) abonniert werden. ## iCal-Import Admins und Superadmins können `.ics` Dateien im Adminbereich hochladen. Termine werden importiert und direkt freigegeben. `GEO:lat;lng` wird unterstützt, um Karten direkt anzuzeigen. ## Standardansicht - Jeder Benutzer hat eine Standardansicht (`/api/views/default`). - Termine können direkt im Kalender oder in der Listenansicht ein- oder ausgeblendet werden. - Kategorien lassen sich abonnieren, um künftige Termine automatisch einzublenden. ## Einstellungen Unter `/settings` können Nutzer ihre E-Mail oder ihr Passwort ändern und den iCal-Link erneuern. ## Superadmin Superadmins sehen unter `/admin` zusätzlich die System-Einstellungen und können den Ortsanbieter (Google oder OpenStreetMap/Nominatim) konfigurieren. Außerdem kann die öffentliche Registrierung deaktiviert werden. Zusätzlich kann die E-Mail-Verifizierung für neue Registrierungen ein- oder ausgeschaltet werden. Der App-Name kann ebenfalls dort gepflegt werden und wird in der Navigation sowie im iCal-Export verwendet. Für iCal wird standardmäßig ein Rückblick von 14 Tagen angewendet (plus alle zukünftigen Termine). Jeder Benutzer kann den Rückblick in den Einstellungen anpassen; der Wert wird als URL-Parameter `pastDays` genutzt. ## Orte & Karten - Der Ort wird per Google Places oder OpenStreetMap (Nominatim) vorgeschlagen und mit `placeId` sowie Koordinaten gespeichert. - Der Ort kann zusätzlich direkt auf der Karte ausgewählt werden (Reverse-Geocoding füllt den Namen). - In den Termin-Details wird abhängig vom Anbieter eine Karte (Google Maps Embed oder OpenStreetMap) angezeigt. ## Passwort-Reset Unter `/reset` kann ein Passwort-Reset angefordert werden. Der Link ist 60 Minuten gültig. Wenn keine SMTP-Umgebung gesetzt ist, wird der Link im Server-Log ausgegeben. ## E-Mail-Verifizierung - Nach der Registrierung wird eine Verifizierungs-Mail gesendet (Token ist 24h gültig). - Verifizierungslink erneut senden unter `/verify`. - Superadmins können die Verifizierung in den System-Einstellungen deaktivieren. ## Termin-Logik - Startdatum und Startzeit sind Pflichtfelder. - Endzeit ist optional; falls nicht gesetzt, wird für Kalenderausgaben automatisch +3 Stunden angenommen. ## Registrierungen - Neue Mitglieder sind standardmäßig `PENDING`. - Admins schalten unter `/admin/users` frei. ## Docker ```bash cp .env.example .env docker compose up --build ``` Wichtig für persistente Logins und Daten: - `NEXTAUTH_SECRET` in `.env` fix setzen (nicht bei jedem Build wechseln). - Die SQLite-DB liegt im Host-Verzeichnis `/opt/docker/vereinskalender/app-data` und wird nach `/app/prisma/data` gemountet. Sie bleibt über Rebuilds erhalten. - `docker compose down -v` löscht keine Bind-Mount-Daten, aber ein Entfernen von `/opt/docker/vereinskalender/app-data` löscht alles. ## Rate Limiting Passwort-Reset, E-Mail-Verifizierung, Registrierung, Login und iCal-Import sind DB-basiert rate-limited. Nach dem Hinzufügen neuer Limits Prisma-Migration ausführen (`npm run prisma:migrate` bzw. `npm run prisma:deploy` im Container). Optional zusätzlich per Nginx: ```nginx limit_req_zone $binary_remote_addr zone=authlimit:10m rate=5r/m; location = /api/password-reset/request { limit_req zone=authlimit burst=5 nodelay; proxy_pass http://app:3000; } location = /api/verify-email/request { limit_req zone=authlimit burst=5 nodelay; proxy_pass http://app:3000; } location = /api/register { limit_req zone=authlimit burst=5 nodelay; proxy_pass http://app:3000; } ``` ## Schnellere Builds (Best Practices) - `package-lock.json` committen und im Dockerfile `npm ci` nutzen (bereits vorbereitet). - BuildKit-Cache nutzen (im Dockerfile aktiv, benötigt Docker BuildKit). - Für schnelle lokale Iteration: `docker compose -f docker-compose.dev.yml up --build`. ## FullCalendar CSS FullCalendar v6 liefert keine fertigen CSS-Dateien auf npm aus. Daher liegt eine gebündelte CSS-Datei in `public/vendor/fullcalendar/fullcalendar.css`, erzeugt aus den offiziellen Stylesheets des FullCalendar-Repos (Version v6.1.20). Bei einem Upgrade die CSS-Datei via `scripts/build-fullcalendar-css.sh` neu generieren. ## Sicherheitshinweise - Keine Secrets committen; `.env` ist in `.dockerignore`. - Für Prod echte Secrets und eine externe DB nutzen. - Login-Versuche sind gedrosselt (IP + E-Mail), nach mehreren Fehlversuchen erfolgt eine temporäre Sperre.