20 Bilder aus der Wegwerf-Instanz: fünf Seiten, je hell und dunkel, Schreibtisch und Handy. Ausschließlich erfundene Daten. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Planer
Aufgaben, Ideen, Projekte und Zeitblöcke an einem Ort.
Die Kernidee: Kalender aus Microsoft 365 (Firma, Gewerbe) und Google (privat) lesen und Aufgaben per Ziehen und Ablegen als Zeitblöcke direkt in diese Kalender schreiben — inklusive Rückweg, wenn ein Block im Kalender verschoben wird.
Stand: Meilenstein 1 von 8. Die Grundlage steht: Anmeldung, Rollen, Protokoll, Bereiche und der Betrieb. Aufgaben kommen mit M2, das Lesen der Kalender mit M3, die Wochenplanung mit M4. Der Fahrplan sagt, was wann folgt.
- Schnellstart – in zehn Minuten von
git clonezur ersten Anmeldung - Handbuch – Anleitung für den Alltag
- Fahrplan – was geplant ist und was bewusst nicht
- Änderungen – was sich je Version getan hat
Funktionsumfang
Heute
- Anmeldung über einen OIDC-Anbieter, über Passkeys oder mit E-Mail und Passwort — je Instanz einzeln zuschaltbar
- Rollen
BenutzerundVerwaltung; jede schreibende Aktion wird serverseitig geprüft und hinterlässt einen Protokolleintrag - Bereiche mit Farbe, Arbeitsfenster und Tageskapazität je Wochentag
- Hell und Dunkel, Oberfläche durchgehend auf Deutsch
- Betrieb über Docker Compose, mit Sicherung, Aktualisierung und Healthcheck
Geplant
Aufgaben und Projekte, Kalender lesen und schreiben, Wochenplanung per Ziehen und Ablegen, Benachrichtigungen, Integrationen, Schnellerfassung vom Handy und ein MCP-Server. Siehe ROADMAP.md.
Bildergalerie
| Hell | Dunkel | |
|---|---|---|
| Anmelden | ![]() |
![]() |
| Start | ![]() |
![]() |
| Bereiche | ![]() |
![]() |
| Einstellungen | ![]() |
![]() |
| Protokoll | ![]() |
![]() |
Alle Bilder entstehen mit npm run screenshots in einer Wegwerf-Instanz mit
ausschließlich erfundenen Daten — niemals aus einer Instanz mit echten Daten.
Die zwei Instanzen
Ein Abbild, zwei Deployments, gesteuert über INSTANZ. Die Umschaltung wird
serverseitig erzwungen: Was eine Instanz nicht darf, liefert dort 404 und
lädt seine Konfiguration gar nicht erst.
firma |
daheim |
|
|---|---|---|
| Anmeldung | OIDC Entra ID | OIDC authentik |
| Kalenderkonten | Graph (Firmen-Tenant) | Graph (Gewerbe-Tenant), Google |
| Graph Firmenpostfach | Graph Gewerbepostfach, Gmail | |
| Integrationen | Zammad, Wartungsplaner, Spiegel senden | Telegram, Spiegel empfangen |
Ein falsch geschriebener Wert bricht den Start ab, statt still auf einen Standard zu fallen — in der Firma dürfen keine Provider anlaufen, die dort nichts zu suchen haben.
Umgebungsvariablen
Alle Werte stehen in .env. Vorlage: .env.example.
.env gehört niemals ins Git.
Pflicht
| Variable | Beispiel | Bedeutung |
|---|---|---|
INSTANZ |
daheim |
firma oder daheim |
APP_URL |
https://planer.example.de |
Öffentliche Adresse, ohne Schrägstrich am Ende |
DATABASE_URL |
postgresql://planer:…@db:5432/planer?schema=public |
Verbindung zur Datenbank |
AUTH_SECRET |
(32 Byte Base64) | Signiert Sitzungen. openssl rand -base64 32 |
WEBAUTHN_RP_ID |
planer.example.de |
Nackte Domain, ohne Schema und Port |
WEBAUTHN_ORIGIN |
https://planer.example.de |
Exakt die Herkunft im Browser |
Datenbank im Compose-Stapel
| Variable | Standard | Bedeutung |
|---|---|---|
POSTGRES_USER |
planer |
Benutzer der Datenbank |
POSTGRES_PASSWORD |
(keiner) | Pflicht. Muss zu DATABASE_URL passen |
POSTGRES_DB |
planer |
Name der Datenbank |
POSTGRES_PORT |
5432 |
Nach außen veröffentlichter Port |
APP_PORT |
3000 |
Nach außen veröffentlichter Port der Anwendung |
Anmeldung
| Variable | Standard | Bedeutung |
|---|---|---|
AUTH_TRUST_HOST |
false |
Hinter einem Reverse Proxy auf true |
ANMELDUNG_PASSWORT |
false |
Anmeldung mit E-Mail und Passwort erlauben |
OIDC_NAME |
(leer) | Beschriftung des Anmeldeknopfs |
OIDC_ISSUER |
(leer) | Issuer-URL des Anbieters |
OIDC_CLIENT_ID |
(leer) | Client-ID |
OIDC_CLIENT_SECRET |
(leer) | Client-Secret |
WEBAUTHN_RP_NAME |
Planer |
Name, den das Gerät beim Passkey anzeigt |
OIDC ist aktiv, sobald Issuer, Client-ID und Secret gesetzt sind. Der
Rückleit-URI beim Anbieter lautet <APP_URL>/api/auth/callback/oidc.
Einrichtung und Sonstiges
| Variable | Standard | Bedeutung |
|---|---|---|
ADMIN_EMAIL |
(leer) | Erstes Verwaltungskonto, wird vom Seed angelegt |
ADMIN_PASSWORT |
(leer) | Mindestens 12 Zeichen. Ohne den Wert entsteht ein Konto, das nur OIDC oder Passkey kennt |
TZ |
Europe/Berlin |
Zeitzone für Anzeigen und Tagesgrenzen |
DEMO_MODUS |
false |
Nur für die Screenshot-Instanz. Im Betrieb niemals true |
Betrieb
Starten und stoppen
docker compose up -d # starten
docker compose logs -f app # Protokoll mitlesen
docker compose down # stoppen, Daten bleiben
Der Stapel besteht aus drei Diensten: db, ein Einmaldienst migration, der
die Migrationen einspielt und sich beendet, und app.
Healthcheck
curl https://planer.example.de/api/health
{
"status": "ok",
"version": "0.1.0",
"instanz": "daheim",
"datenbank": "ok",
"zeit": "2026-03-17T08:20:00.000Z"
}
Die Auskunft ist bewusst ohne Anmeldung erreichbar und enthält deshalb keine
Zählstände, Namen oder Adressen. Bei "status": "fehler" antwortet der
Endpunkt mit HTTP 503.
Aktualisieren
./update.sh # auf den neuesten Tag
./update.sh --nur-pruefen # nur zeigen, was passieren würde
./update.sh --version v0.2.0 # auf eine bestimmte Version
./update.sh --ja # ohne Rückfrage
Das Skript prüft die Voraussetzungen, zeigt die Änderungen aus dem CHANGELOG,
legt eine Sicherung an, wechselt auf den Ziel-Tag, baut, startet und wartet
auf einen gesunden /api/health mit der erwarteten Version. Klappt das nicht,
rollt es auf die vorherige Version zurück und nennt den Pfad der Sicherung.
Jeder Lauf wird nach logs/update-<Datum>.log protokolliert.
Die Datenbank wird beim Zurückrollen nicht automatisch zurückgespielt — das wäre bei einem Teilfehler gefährlicher als hilfreich. Das Skript nennt den Befehl dafür.
Sicherung und Wiederherstellung
./backup.sh # Datenbank, .env und Uploads
./backup.sh --tage 90 # längere Aufbewahrung
./backup.sh --nur-datenbank # nur der Dump
Je Lauf entstehen zwei Dateien in sicherungen/:
ein pg_dump im Custom-Format und ein tar.gz mit .env und uploads.
Wiederherstellen:
# Datenbank
docker compose exec -T db pg_restore -U planer -d planer --clean --if-exists \
< sicherungen/planer-db-2026-03-17_02-00-00.dump
# Dateien
tar -xzf sicherungen/planer-dateien-2026-03-17_02-00-00.tar.gz
Die Sicherung enthält .env und damit alle Geheimnisse. Sie gehört so
geschützt wie die Datenbank selbst.
Nächtlich per cron:
0 2 * * * cd /opt/planer && ./backup.sh --tage 30 >> logs/backup.log 2>&1
Entwicklung
npm install
docker compose up -d db # nur die Datenbank
npm run db:migrieren # Migrationen einspielen
npm run db:seed # Bereiche und Verwaltungskonto
npm run dev # Entwicklungsserver
Prüfungen
npm run pruefen # Prettier, ESLint, tsc, Vitest, ShellCheck
npm run test:integration # gegen eine echte Datenbank
npm run test:migration # Migrationen gegen eine Wegwerf-Datenbank
npm run test:e2e # Playwright gegen die gebaute Anwendung
npm run screenshots # Handbuchbilder neu aufnehmen
npm run pruefen muss vor jedem Commit grün sein. Fehlt shellcheck lokal,
wird das Docker-Abbild koalaman/shellcheck benutzt.
Aufbau
prisma/ Schema, Migrationen, Seed, Demo-Seed
scripts/ Prüf-, Test- und Betriebsskripte
src/app/ Seiten und API-Routen; (app) = angemeldeter Bereich
src/components/ Bausteine; ui/ enthält die Primitiven
src/lib/ Reine Logik ohne DB und ohne Systemzeit, je Datei ein Test
src/server/ DB-nahe Dienste, Server Actions, Provider
e2e/ Playwright-Rauchtests
screenshots/ Aufnahme der Handbuchbilder
docs/ Schnellstart, Handbuch, Bilder
devlog/ Entwicklungstagebuch und Zeiterfassung
Reine Funktionen bekommen die aktuelle Zeit als Parameter, damit sie ohne Tricks prüfbar bleiben. Die Tests laufen bewusst in UTC — so fällt sofort auf, wenn irgendwo die Ortszeit des Servers durchschlägt statt Europe/Berlin.
Bekannte Meldungen
npm audit meldet vier Funde in transitiven Abhängigkeiten der
prisma-CLI (deepmerge-ts über @prisma/config, mysql2). Beide sind
Entwicklungsabhängigkeiten und erreichen das Laufzeitabbild nicht; MySQL wird
ohnehin nie geladen. Der angebotene Fix wäre ein Rückschritt auf Prisma 6.
Sicherheit
- Anmeldung, Passwort und Passkey antworten bei Fehlschlag immer gleich, damit sich nicht abfragen lässt, wer hier ein Konto hat.
- Sitzungen sind JWT, aber Rolle und Sperrstatus werden bei jeder Anfrage frisch aus der Datenbank gelesen — eine Sperre wirkt sofort.
- Jede schreibende Aktion prüft die Rolle in der Seite und noch einmal in der Server Action. Die Oberfläche blendet Knöpfe nur zusätzlich aus.
- Audit-Details werden vor dem Speichern gesäubert: Felder, deren Name nach Passwort, Token, Betreff oder Mailinhalt aussieht, werden ersetzt.
- Keine Geheimnisse, Tokens, Mailinhalte oder Termin-Betreffs in Protokollen.
Gefundene Schwachstellen bitte nicht als Issue anlegen, sondern direkt melden.
Lizenz
Privates Projekt, keine Lizenz zur Weiterverwendung.









