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 Seitencomponents/- UI-Komponenten (Kalender, Admin-Panel, View-Manager)app/api/- API-Routen (Auth, Events, Views, iCal)prisma/- Schema und Migrationslib/- Prisma Client, Auth-Helfer
Lokale Entwicklung
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 <noreply@example.com>"
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 Entwicklungnpm run build- Production Buildnpm 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- RegistrierungGET /api/events- Events (Admins sehen alles, User nur eigene + freigegebene)POST /api/events- Termin vorschlagen/anlegenPATCH /api/events/:id- Freigeben/Ablehnen (Admin)GET /api/categories- Kategorien anzeigenPOST /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 AnsichtenPOST /api/views- Ansicht erstellenPOST /api/views/:id/items- Termin zur AnsichtDELETE /api/views/:id/items- Termin entfernenGET /api/views/default- Standardansicht laden/erstellenGET /api/ical/:token- iCal Feed der AnsichtPOST /api/ical/import- iCal-Datei importieren (Admin/Superadmin)POST /api/views/default/rotate- iCal-Link erneuernPOST /api/views/:id/categories- Kategorie abonnierenDELETE /api/views/:id/categories- Kategorie-Abo entfernenPATCH /api/profile- E-Mail/Passwort aktualisierenPOST /api/password-reset/request- Passwort-Reset Link anfordernPOST /api/password-reset/confirm- Passwort setzen (mit Token)POST /api/verify-email/request- Verifizierungslink sendenPOST /api/verify-email/confirm- E-Mail verifizierenGET /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 abrufenPOST /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
placeIdsowie 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/usersfrei.
Docker
cp .env.example .env
docker compose up --build
Wichtig für persistente Logins und Daten:
NEXTAUTH_SECRETin.envfix setzen (nicht bei jedem Build wechseln).- Die SQLite-DB liegt im Host-Verzeichnis
/opt/docker/vereinskalender/app-dataund wird nach/app/prisma/datagemountet. Sie bleibt über Rebuilds erhalten. docker compose down -vlöscht keine Bind-Mount-Daten, aber ein Entfernen von/opt/docker/vereinskalender/app-datalö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:
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.jsoncommitten und im Dockerfilenpm cinutzen (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;
.envist 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.