- Add web app manifest, maskable/apple-touch icons, and a production-only service worker that caches the app shell for offline start - Improve mobile UX: responsive breakpoints from ~320px, 44px touch targets, safe-area insets, dvh heights, 16px inputs to avoid iOS zoom - Serve .webmanifest with correct content type in the Go static handler - Document PWA behavior and mobile features in the README
151 lines
8.2 KiB
Markdown
151 lines
8.2 KiB
Markdown
# WannPassts
|
||
|
||
**Deine freien Zeiten – geteilt per Link. Ohne deine Termine preiszugeben.**
|
||
|
||
WannPassts ist eine Self-Hosted-Buchungs-App (à la Cal.com / Calendly, aber privat):
|
||
Du verbindest deine Kalender, teilst einen Link, und andere sehen nur **frei / belegt**
|
||
und stellen Buchungsanfragen. Termintitel, Orte und Beschreibungen verlassen deine
|
||
Kalender nie.
|
||
|
||
## Features
|
||
|
||
- 🔐 **Registrierung/Login** (JWT, bcrypt-Passwort-Hashes)
|
||
- 📆 **Kalender-Connect:**
|
||
- **Google Kalender** per OAuth – nutzt die FreeBusy-API, die *strukturell* keine Termindetails liefert
|
||
- **iCloud** & andere (Fastmail, Nextcloud, …) per **CalDAV** – iCloud mit App-spezifischem Passwort
|
||
- **Beliebige ICS-/webcal-Links** (z. B. Google-„Private Adresse“, Ferien-/Schichtpläne)
|
||
- 🔗 **Öffentliche Buchungsseite** (`/b/<slug>`): freie Slots im von dir definierten Zeitfenster, Wochentage, Slot-Raster und Dauern – **niemals Termindetails**
|
||
- 📨 **Buchungsanfragen** annehmen/ablehnen; angenommene Zeiten werden belegt und verhindern Doppelbuchungen
|
||
- ⚙️ Buchungsregeln: Zeitfenster, Slot-Raster (10–60 Min), Dauern, Wochentage, Horizont (1–120 Tage), Zeitzone
|
||
- 🧊 **Liquid-Glass-UI** mit **Dark-/Light-Mode**: Umschalter (Auto / Dunkel / Hell) auf jeder Seite, automatische Systemerkennung (`prefers-color-scheme`) inkl. Live-Wechsel, Auswahl wird pro Browser gespeichert
|
||
- 📱 **Voll mobilfähig**: responsive Layouts ab ~320 px, 44px-Touch-Targets, kein iOS-Fokus-Zoom (16px-Inputs), Safe-Area-Unterstützung (Notch), `dvh`-Höhen, `touch-action` ohne Doppeltipp-Zoom
|
||
- 📲 **PWA**: installierbar (Android/Desktop: „App installieren“, iOS: „Zum Home-Bildschirm“), Manifest mit Maskable-Icons, App-Shortcuts; Service Worker cached die App-Shell → Startseite funktioniert offline (API-Aufrufe brauchen natürlich Netz)
|
||
- 🔒 Provider-Tokens werden **AES-256-GCM-verschlüsselt** in der SQLite-DB gespeichert
|
||
|
||
## Tech Stack
|
||
|
||
| Teil | Technologie |
|
||
| -------- | ------------------------------------------------------------------ |
|
||
| Frontend | Vue 3 + TypeScript, Vite, Pinia, Vue Router (kein UI-Framework) |
|
||
| Backend | Go, chi, JWT, SQLite (modernc, CGO-frei), x/oauth2, go-webdav |
|
||
|
||
## Schnellstart (Entwicklung)
|
||
|
||
```bash
|
||
# Terminal 1 – Backend (http://localhost:8080)
|
||
cd backend
|
||
cp .env.example .env # optional anpassen; ohne .env läuft es mit Defaults
|
||
go run ./cmd/server
|
||
|
||
# Terminal 2 – Frontend (http://localhost:5173, Proxied /api → 8080)
|
||
cd frontend
|
||
npm install
|
||
npm run dev
|
||
```
|
||
|
||
Dann http://localhost:5173 öffnen, Konto erstellen, Kalender verbinden, Link teilen. 🎉
|
||
|
||
## Produktion (ein einziger Go-Prozess)
|
||
|
||
```bash
|
||
cd frontend && npm run build && cd ..
|
||
cd backend
|
||
STATIC_DIR=../frontend/dist \
|
||
APP_URL=https://deine-domain.de \
|
||
FRONTEND_URL=https://deine-domain.de \
|
||
JWT_SECRET=$(openssl rand -hex 32) \
|
||
ENCRYPTION_KEY=$(openssl rand -hex 32) \
|
||
go run ./cmd/server
|
||
```
|
||
|
||
Der Go-Server liefert dann das Frontend (`dist/`) **und** die API über einen Port.
|
||
|
||
### PWA-Hinweise
|
||
|
||
- Der Service Worker wird nur im Production-Build registriert (`npm run build` + Auslieferung über `STATIC_DIR`), nicht im Vite-Dev-Modus.
|
||
- Die Cache-Version des Service Workers wird pro Build automatisch neu gesetzt (Build-Zeitstempel) — nach einem Deploy holen Clients die neue Version und alte Caches werden geräumt.
|
||
- `/api/*` wird niemals gecached; offline funktioniert die App-Shell (Startseite/Navigation), Kalender- und Buchungsdaten benötigen eine Verbindung.
|
||
- Icons liegen als SVG-Quellen in `frontend/public/icons/` und werden per `rsvg-convert` zu PNGs konvertiert (Neuerzeugung: siehe Skript unten im Abschnitt „Tests“).
|
||
|
||
## Kalender-Anbindung einrichten
|
||
|
||
### Google
|
||
1. [Google Cloud Console](https://console.cloud.google.com): Projekt anlegen, **Google Calendar API** aktivieren.
|
||
2. OAuth-Zustimmungsbildschirm (External) einrichten, Scope `calendar.readonly`.
|
||
3. **OAuth-Client-ID** (Webanwendung) erstellen; als autorisierte Redirect-URI
|
||
`{APP_URL}/api/calendars/google/callback` eintragen (lokal: `http://localhost:8080/api/calendars/google/callback`).
|
||
4. `GOOGLE_CLIENT_ID` + `GOOGLE_CLIENT_SECRET` in `backend/.env` setzen.
|
||
|
||
Beim Verbinden wird nur der **Primärkalender** angebunden; gelesen wird ausschließlich Free/Busy.
|
||
|
||
### iCloud
|
||
1. Auf [appleid.apple.com](https://appleid.apple.com/account/manage) → *Anmeldung und Sicherheit* → **App-spezifisches Passwort** erzeugen.
|
||
2. In WannPassts: *Kalender → iCloud / CalDAV*:
|
||
- Server: `https://caldav.icloud.com`
|
||
- Benutzername: Apple-ID (E-Mail)
|
||
- Passwort: das App-spezifische Passwort (nicht das normale!)
|
||
3. Der erste Kalender mit Terminen wird automatisch erkannt (Kalender-Pfad ist optional manuell überschreibbar).
|
||
|
||
### Andere (Fastmail, Nextcloud, GMX, …)
|
||
Gleiche CalDAV-Maske mit dem jeweiligen CalDAV-Server des Anbieters, oder beliebige **ICS-Links** abonnieren.
|
||
|
||
## Datenschutz-Design
|
||
|
||
- Besucher sehen ausschließlich **Start/Ende von busy-Zeiträumen** – die API hat gar keine Felder für Titel & Co.
|
||
- Google: FreeBusy-API liefert von sich aus nur Belegungszeiträume.
|
||
- CalDAV/ICS: Termine werden serverseitig auf `DTSTART/DTEND` reduziert, abgesagte und „Verfügbar“-Termine (TRANSPARENT) werden ignoriert.
|
||
- Tokens/Passwörter der Verbindungen liegen verschlüsselt (AES-GCM aus `ENCRYPTION_KEY`) in der DB.
|
||
- Sync erfolgt bedarfsgesteuert (max. alle 5 Min) beim Aufruf der Buchungsseite plus manueller Button.
|
||
|
||
## Bekannte Grenzen (MVP)
|
||
|
||
- **Kein E-Mail-Versand** – Anfragen/Freigaben erscheinen nur im Dashboard (Mail-Versand via SMTP wäre der nächste Ausbauschritt).
|
||
- **ICS-Abos** expandieren Serientermine nicht**; CalDAV und Google tun dies serverseitig korrekt.
|
||
- **Ausstehende (pending) Anfragen blockieren noch keine Zeit** – erst angenommene. Bei zwei parallelen Anfragen für denselben Slot gewinnt, wer zuerst angenommen wird (Kollision wird beim Annehmen geprüft).
|
||
- Ein Google-Konto verbindet aktuell den Primärkalender; weitere Kalender lassen sich als ICS-Abo hinzufügen.
|
||
|
||
## Projektstruktur
|
||
|
||
```
|
||
backend/
|
||
cmd/server/ # Einstieg
|
||
internal/config/ # Env-/Secret-Handling
|
||
internal/db/ # SQLite-Schema & Queries
|
||
internal/crypto/ # AES-GCM für gespeicherte Provider-Tokens
|
||
internal/calendar/ # Provider: google.go, caldav.go, ics.go (+ Syncer)
|
||
internal/httpapi/ # Router, Middleware, Handler (auth, calendars, bookings, public)
|
||
frontend/
|
||
src/lib/ # api-Client, Typen, Zeitzonen-Helfer
|
||
src/stores/ # Pinia: auth, toast
|
||
src/views/ # Landing, Login, Register, Dashboard, Booking (/b/:slug)
|
||
src/components/ # GlassCard, ShareLink, Sektionen (Kalender/Anfragen/Einstellungen)
|
||
src/styles/main.css # Liquid-Glass-Dark-Designsystem
|
||
```
|
||
|
||
## API-Überblick
|
||
|
||
| Methode | Pfad | Auth | Zweck |
|
||
| ------------------- | --------------------------------- | ---- | ---------------------------------------- |
|
||
| POST | `/api/auth/register` \| `/login` | – | Konto / JWT |
|
||
| GET/PATCH | `/api/me` | ✅ | Profil & Buchungsregeln |
|
||
| POST | `/api/me/slug` | ✅ | Neuen Buchungslink erzeugen |
|
||
| GET/DELETE | `/api/calendars…` | ✅ | Verbinden (google/caldav/ics), verwalten, sync |
|
||
| GET | `/api/public/{slug}` | – | Buchungsseite: Regeln + busy-Zeiträume |
|
||
| POST | `/api/public/{slug}/bookings` | – | Buchungsanfrage stellen |
|
||
| GET | `/api/bookings`, `…/accept\|decline` | ✅ | Anfragen verwalten |
|
||
|
||
## Tests
|
||
|
||
```bash
|
||
cd backend && go test ./... # ICS-Parser, Dauer-Handling, Interval-Clamping
|
||
```
|
||
|
||
PWA-Icons neu erzeugen (benötigt `rsvg-convert`):
|
||
|
||
```bash
|
||
cd frontend/public/icons
|
||
rsvg-convert -w 192 -h 192 -o icon-192.png icon.svg
|
||
rsvg-convert -w 512 -h 512 -o icon-512.png icon.svg
|
||
rsvg-convert -w 512 -h 512 -o icon-maskable-512.png icon-maskable.svg
|
||
rsvg-convert -w 180 -h 180 -o apple-touch-icon.png apple-touch-icon.svg
|
||
```
|