2026-01-16 23:07:13 +01:00
2026-01-16 23:07:13 +01:00
2026-01-16 23:02:36 +01:00
2026-01-16 23:02:36 +01:00
2026-01-16 23:02:36 +01:00
2026-01-15 16:24:09 +01:00
2026-01-15 16:24:09 +01:00
2026-01-15 23:18:42 +01:00
2026-01-15 16:24:09 +01:00
2026-01-15 23:18:42 +01:00
2026-01-13 18:08:59 +01:00
2026-01-15 16:24:09 +01:00
2026-01-15 23:18:42 +01:00
2026-01-15 16:24:09 +01:00
2026-01-15 23:18:42 +01:00
2026-01-15 16:24:09 +01:00
2026-01-15 16:24:09 +01:00
2026-01-15 16:24:09 +01:00
2026-01-15 16:24:09 +01:00
2026-01-15 16:24:09 +01:00
2026-01-13 18:08:59 +01:00
2026-01-13 18:08:59 +01:00
2026-01-15 16:24:09 +01:00
2026-01-13 18:08:59 +01:00
2026-01-15 23:18:42 +01:00
2026-01-15 16:24:09 +01:00
2026-01-13 18:08:59 +01:00

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.
  • 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

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 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)
  • 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.

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.

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

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:

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.
Description
No description provided
Readme 1.8 MiB
Languages
TypeScript 95.3%
CSS 3.1%
Shell 1.2%
Dockerfile 0.3%