feat: Schritte 14–15 — Google Calendar OAuth + Apple CalDAV Sync + Settings-Seite

- server/services/google-calendar.js: OAuth 2.0, bidirektionaler Sync via
  Google Calendar API v3, inkrementeller syncToken, 410-Fallback auf Vollsync
- server/services/apple-calendar.js: CalDAV via tsdav (dynamic ESM import),
  minimaler ICS-Parser + ICS-Builder, bidirektionaler Sync
- server/routes/calendar.js: 7 neue Sync-Routen (google/auth, google/callback,
  google/sync, google/status, google/disconnect, apple/status, apple/sync)
- server/db.js: Migration 2 — sync_config Tabelle + idx_calendar_external_id
- server/db-schema-test.js: MIGRATIONS_SQL[2] für Tests synchronisiert
- server/auth.js: PATCH /me/password Endpoint
- server/index.js: Auto-Sync-Scheduler (setInterval, SYNC_INTERVAL_MINUTES)
- public/pages/settings.js: vollständige Settings-Seite (Konto, Passwort,
  Kalender-Sync-Status + Aktionen, Familienmitglieder-Verwaltung)
- public/styles/settings.css: neue Stylesheet-Datei
- public/index.html + public/sw.js: settings.css eingebunden und gecacht
- .env.example: SYNC_INTERVAL_MINUTES ergänzt
- README.md: vollständige Setup-Anleitung, Google/Apple-Sync-Dokumentation,
  modernes GitHub-Layout mit Badges und aufklappbaren Abschnitten

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ulsklyc
2026-03-24 22:53:44 +01:00
parent 81d4000ee1
commit 72d6d5126e
13 changed files with 1693 additions and 131 deletions
+211 -105
View File
@@ -1,178 +1,284 @@
# Oikos — Selbstgehosteter Familienplaner
<div align="center">
Oikos ist eine self-hosted Progressive Web App für Familien. Sie läuft vollständig auf deinem eigenen Server — keine Cloud-Abhängigkeiten, keine Datenweitergabe.
# 🏠 Oikos
## Features
**Selbstgehosteter Familienplaner — privat, offen, ohne Abonnement**
- **Dashboard** — Begrüßung, Wetter-Widget, anstehende Termine, dringende Aufgaben, Essen, Pinnwand
- **Aufgaben** — Listenansicht (Kategorie/Fälligkeit), Kanban-Board, Teilaufgaben, Swipe-Gesten, wiederkehrende Aufgaben
- **Einkaufslisten** — Mehrere Listen, Kategorien, Essensplan-Integration
- **Essensplan** — Wochenansicht, Zutatenverwaltung, Übertrag auf Einkaufsliste
- **Kalender** — Monats-/Wochen-/Tages-/Agenda-Ansicht, Familienfarben, wiederkehrende Termine
- **Pinnwand** — Farbige Sticky Notes mit Markdown-Light
- **Kontakte** — Wichtige Kontakte mit Kategorie-Filter, tel:/mailto:/Maps-Links
- **Budget** — Einnahmen/Ausgaben, Monatsauswertung, CSV-Export
- **PWA** — Offline-fähig, installierbar auf iOS/Android/Desktop
[![Node.js](https://img.shields.io/badge/Node.js-%3E%3D20-339933?logo=node.js&logoColor=white)](https://nodejs.org)
[![Docker](https://img.shields.io/badge/Docker-ready-2496ED?logo=docker&logoColor=white)](https://www.docker.com)
[![SQLite](https://img.shields.io/badge/SQLite-SQLCipher%20verschlüsselt-003B57?logo=sqlite&logoColor=white)](https://www.zetetic.net/sqlcipher/)
[![PWA](https://img.shields.io/badge/PWA-offline--fähig-5A0FC8?logo=pwa&logoColor=white)](https://web.dev/progressive-web-apps/)
[![Lizenz](https://img.shields.io/badge/Lizenz-privat-lightgrey)](./LICENSE)
## Voraussetzungen
Alle Daten bleiben auf deinem eigenen Server.
Kein Cloud-Zwang. Keine Datenweitergabe. Kein Tracking.
- **Docker & Docker Compose** (empfohlen) oder **Node.js ≥ 20**
- Ein Linux-Server hinter Nginx Reverse Proxy mit SSL (empfohlen)
[Module](#module) · [Schnellstart](#schnellstart) · [Konfiguration](#konfiguration) · [Kalender-Sync](#kalender-synchronisation) · [Sicherheit](#sicherheit)
</div>
---
## Schnellstart mit Docker (empfohlen)
## Module
### 1. Repository klonen
| | Modul | Highlights |
|---|---|---|
| 📋 | **Dashboard** | Wetter-Widget, anstehende Termine, dringende Aufgaben, Essen heute, Pinnwand-Vorschau |
| ✅ | **Aufgaben** | Listenansicht + Kanban, Teilaufgaben, Swipe-Gesten, wiederkehrende Aufgaben (RRULE) |
| 🛒 | **Einkauf** | Mehrere Listen, automatische Kategorie-Sortierung, Integration mit Essensplan |
| 🍽️ | **Essensplan** | Wochenansicht, Zutatenverwaltung, Zutaten → Einkaufsliste mit einem Klick |
| 📅 | **Kalender** | Monats-/Wochen-/Tages-/Agenda-Ansicht, Google Calendar & Apple Calendar Sync |
| 📌 | **Pinnwand** | Farbige Sticky Notes, Markdown-Light (fett, kursiv, Listen) |
| 👥 | **Kontakte** | Wichtige Familien-Kontakte, Direktanruf (`tel:`), Maps-Links |
| 💰 | **Budget** | Einnahmen/Ausgaben, Kategorien, Monatsvergleich, CSV-Export |
| ⚙️ | **Einstellungen** | Passwort ändern, Kalender-Sync verwalten, Familienmitglieder anlegen |
---
## Tech Stack
**Backend:** Node.js · Express · SQLite/SQLCipher · express-session · bcrypt
**Frontend:** Vanilla JavaScript (ES-Module) · Kein Framework · Kein Build-Step
**Deployment:** Docker · Nginx Reverse Proxy · PWA (Service Worker + Manifest)
**Optional:** Google Calendar API v3 (OAuth 2.0) · Apple iCloud CalDAV (tsdav)
---
## Schnellstart
### Voraussetzungen
- **Docker** + **Docker Compose**
- Ein Linux-Server mit Nginx Reverse Proxy und SSL (empfohlen: [Nginx Proxy Manager](https://nginxproxymanager.com))
### 1 — Repository klonen
```bash
git clone https://github.com/ulsklyc/oikos.git
cd oikos
```
### 2. Umgebungsvariablen konfigurieren
### 2 Umgebungsvariablen setzen
```bash
cp .env.example .env
```
Pflichtfelder in `.env` anpassen:
Mindestens diese zwei Pflichtfelder in `.env` ausfüllen:
```env
SESSION_SECRET=ein-langer-zufaelliger-string-min-32-zeichen
DB_ENCRYPTION_KEY=ein-starkes-passwort-fuer-die-datenbank
# Langen zufälligen String (≥ 32 Zeichen)
SESSION_SECRET=...
# AES-256-Schlüssel für SQLCipher-Datenbankverschlüsselung
DB_ENCRYPTION_KEY=...
```
Optional: Wetter-Widget aktivieren
> Vollständige Variablen-Referenz → [Konfiguration](#konfiguration)
```env
OPENWEATHER_API_KEY=dein-api-key-von-openweathermap.org
OPENWEATHER_CITY=Berlin
```
### 3. Starten
### 3 — Container starten
```bash
docker compose up -d
```
Die App ist unter `http://localhost:3000` erreichbar.
> Der erste Build dauert 23 Minuten (SQLCipher wird gegen better-sqlite3 kompiliert).
### 4. Erster Login
Beim ersten Start wird automatisch ein Admin-Account erstellt:
```
Benutzername: admin
Passwort: admin
```
**Passwort sofort ändern!** → Einstellungen → Passwort ändern
---
## Ohne Docker (direkt mit Node.js)
### 4 — Admin-Account anlegen
```bash
npm install
cp .env.example .env
# .env anpassen (siehe oben)
npm start
docker compose exec oikos node setup.js
```
Entwicklungsmodus (Auto-Reload):
Das interaktive Script fragt nach Benutzername, Anzeigename und Passwort. Dieser Account hat Admin-Rechte und kann weitere Familienmitglieder anlegen.
```bash
npm run dev
```
### 5 — App öffnen
`http://localhost:3000` — oder die konfigurierte Domain nach dem Nginx-Setup.
---
## Nginx Reverse Proxy
Beispiel-Konfiguration für Nginx Proxy Manager oder direktes Nginx:
Die Datei [`nginx.conf.example`](./nginx.conf.example) enthält eine vollständige Konfiguration.
```nginx
server {
listen 443 ssl;
server_name oikos.deine-domain.de;
**Mit Nginx Proxy Manager:**
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
1. Neuen Proxy Host anlegen: `oikos.deine-domain.de``localhost:3000`
2. SSL-Zertifikat via Let's Encrypt ausstellen
3. Inhalt aus `nginx.conf.example` im Feld "Advanced" eintragen
**Wichtig:** `X-Forwarded-Proto` muss gesetzt sein (in der Vorlage enthalten), damit Session-Cookies in Produktion korrekt als `Secure` gesetzt werden.
---
## Konfiguration
### Pflicht
| Variable | Beschreibung |
|---|---|
| `SESSION_SECRET` | Zufälliger String ≥ 32 Zeichen für Session-Signing |
| `DB_ENCRYPTION_KEY` | SQLCipher AES-256-Schlüssel (leer = keine Verschlüsselung) |
### Wetter-Widget
Kostenlosen API-Key bei [openweathermap.org](https://openweathermap.org/api) registrieren:
```env
OPENWEATHER_API_KEY=...
OPENWEATHER_CITY=Berlin
OPENWEATHER_UNITS=metric # metric = °C, imperial = °F
OPENWEATHER_LANG=de
```
Die Datei `nginx.conf.example` im Repository enthält eine vollständige Konfiguration.
### Weitere Optionen
| Variable | Standard | Beschreibung |
|---|---|---|
| `PORT` | `3000` | Server-Port |
| `NODE_ENV` | `development` | `production` für Deployment |
| `DB_PATH` | `./oikos.db` | Pfad zur SQLite-Datei |
| `SYNC_INTERVAL_MINUTES` | `15` | Automatischer Kalender-Sync-Intervall |
| `RATE_LIMIT_MAX_ATTEMPTS` | `5` | Max. Login-Versuche pro Minute |
Vollständige Vorlage: [`.env.example`](./.env.example)
---
## Familienmitglieder verwalten
## Kalender-Synchronisation
Neue Mitglieder können nur Admins anlegen:
### Google Calendar
1. **Einstellungen****Familienmitglieder****+ Neu**
2. Benutzername, Anzeigename, Passwort, Avatarfarbe und Rolle festlegen
3. Login-Daten dem Familienmitglied mitteilen
<details>
<summary>Einrichtung anzeigen</summary>
#### Google Cloud Console vorbereiten
1. Projekt unter [console.cloud.google.com](https://console.cloud.google.com) anlegen
2. **Google Calendar API** aktivieren
3. **OAuth 2.0-Client-ID** erstellen (Typ: „Webanwendung")
4. Autorisierte Redirect-URI eintragen:
```
https://oikos.deine-domain.de/api/v1/calendar/google/callback
```
5. In `.env` eintragen:
```env
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_REDIRECT_URI=https://oikos.deine-domain.de/api/v1/calendar/google/callback
```
6. Container neu starten: `docker compose up -d`
#### Verbindung herstellen
1. Mit einem **Admin**-Konto einloggen
2. **Einstellungen → Kalender-Synchronisation → Mit Google verbinden**
3. Google-Konto autorisieren → automatische Weiterleitung zurück
**Sync-Verhalten:**
- Erster Sync: Events der letzten 3 Monate + nächsten 12 Monate
- Folge-Syncs: nur Änderungen via Google syncToken (effizient)
- Outbound: neue lokale Termine werden nach Google übertragen
- Konflikt: Google gewinnt bei gleichzeitiger Änderung
</details>
### Apple Calendar (iCloud CalDAV)
<details>
<summary>Einrichtung anzeigen</summary>
#### App-spezifisches Passwort erstellen
1. [appleid.apple.com](https://appleid.apple.com) → „Anmeldung und Sicherheit" → „App-spezifische Passwörter"
2. Neues Passwort für „Oikos" erstellen
3. In `.env` eintragen:
```env
APPLE_CALDAV_URL=https://caldav.icloud.com
APPLE_USERNAME=deine@apple-id.de
APPLE_APP_SPECIFIC_PASSWORD=xxxx-xxxx-xxxx-xxxx
```
4. Container neu starten: `docker compose up -d`
Der Sync-Button erscheint automatisch in den Einstellungen.
</details>
---
## Umgebungsvariablen — Referenz
## Familienmitglieder
| Variable | Pflicht | Standard | Beschreibung |
|---|---|---|---|
| `PORT` | — | `3000` | Server-Port |
| `NODE_ENV` | — | `development` | `production` für Deployment |
| `SESSION_SECRET` | ✓ | — | Langer Zufalls-String (≥ 32 Zeichen) |
| `DB_PATH` | — | `./oikos.db` | Pfad zur SQLite-Datenbankdatei |
| `DB_ENCRYPTION_KEY` | — | — | SQLCipher-Schlüssel (leer = keine Verschlüsselung) |
| `OPENWEATHER_API_KEY` | — | — | API-Key von openweathermap.org |
| `OPENWEATHER_CITY` | — | `Berlin` | Stadtname für Wetter-Abfrage |
| `OPENWEATHER_UNITS` | — | `metric` | `metric` = °C, `imperial` = °F |
| `OPENWEATHER_LANG` | — | `de` | Sprache der Wetterbeschreibungen |
| `RATE_LIMIT_WINDOW_MS` | — | `60000` | Login Rate-Limit Fenster (ms) |
| `RATE_LIMIT_MAX_ATTEMPTS` | — | `5` | Max. Login-Versuche pro Fenster |
Neue Mitglieder können nur Admins anlegen — es gibt keinen öffentlichen Registrierungs-Endpoint.
**Im Browser:** Einstellungen → Familienmitglieder → Mitglied hinzufügen
**Per Script** (z.B. für weiteren Admin):
```bash
docker compose exec oikos node setup.js
```
---
## Sicherheit
## Updates
- Sessions sind `httpOnly`, `SameSite=Strict` und in Produktion `Secure`
- CSRF-Schutz via Double Submit Cookie auf allen zustandsändernden Requests
- Passwörter mit bcrypt (Cost Factor 12) gehasht
- Globaler Rate-Limiter auf allen API-Endpoints (300 req/min)
- Strikter Login-Rate-Limiter (5 Versuche/Minute)
- Content Security Policy via Helmet
- Datenbank optional mit SQLCipher verschlüsselt
```bash
git pull
docker compose up -d --build
```
Datenbank-Migrationen laufen automatisch beim Start. Daten im Volume `oikos_data` bleiben erhalten.
---
## Entwicklung
```bash
npm install
cp .env.example .env
# SESSION_SECRET setzen — DB_ENCRYPTION_KEY weglassen (kein SQLCipher lokal)
npm run dev # Server mit Auto-Reload
```
```bash
npm test # 146 Tests, 7 Suiten (In-Memory-SQLite, keine laufende App nötig)
```
---
## Datensicherung
Die gesamte Datenbank liegt in einer einzigen Datei:
```bash
# Backup
cp /data/oikos.db /backup/oikos-$(date +%Y%m%d).db
# Backup erstellen
docker run --rm \
-v oikos_oikos_data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/oikos-backup-$(date +%Y%m%d).tar.gz /data
# Docker-Volume sichern
docker run --rm -v oikos_data:/data -v $(pwd):/backup \
alpine tar czf /backup/oikos-backup.tar.gz /data
# Backup wiederherstellen
docker compose down
docker run --rm \
-v oikos_oikos_data:/data \
-v $(pwd):/backup \
alpine tar xzf /backup/oikos-backup-YYYYMMDD.tar.gz -C /
docker compose up -d
```
---
## Tests ausführen
## Sicherheit
```bash
npm test
```
Die Tests verwenden In-Memory-SQLite und benötigen keine laufende App-Instanz.
- Sessions: `httpOnly`, `SameSite=Strict`, `Secure` in Produktion, 7 Tage TTL
- CSRF-Schutz via Double Submit Cookie auf allen schreibenden Requests
- Passwörter mit bcrypt (Cost Factor 12) gehasht
- Login-Rate-Limit: 5 Versuche/Minute
- API-Rate-Limit: 300 Requests/Minute pro IP
- Content Security Policy via Helmet
- Datenbank optional mit SQLCipher AES-256 verschlüsselt (im Docker-Container)
- Kein API-Endpoint ohne Session-Auth erreichbar (außer `/api/v1/auth/login`)
---