diff --git a/SPEC.md b/SPEC.md index f6efb42..3bd8b47 100644 --- a/SPEC.md +++ b/SPEC.md @@ -194,6 +194,12 @@ Unverändert gegenüber v1: - **Mehrere Empfänger je Kontakt, Vorlauf-Erinnerungen** (z. B. "in 3 Tagen") sind funktional einfach nachrüstbar, aktuell aber nicht Teil des Scopes. +- **Contact Detail Page: UID statt DB-ID**: Der aktuelle Lookup + `WHERE id = %s` nutzt die Auto-Increment-ID, die sich bei Re-Syncs + ändern kann. Stabilere Alternative: `WHERE uid = %s AND account = %s`, + da die UID aus CardDAV konstant bleibt. Erfordert Änderungen an + Routes (`/contacts/{account}/{uid}`), Templates, Mailer-URLs und + Schema. Details siehe `feature-contact-uid-lookup.md`. ## 12. Web-Ansicht und API (v3, im selben Repo/Image) diff --git a/feature-contact-uid-lookup.md b/feature-contact-uid-lookup.md new file mode 100644 index 0000000..26d0be6 --- /dev/null +++ b/feature-contact-uid-lookup.md @@ -0,0 +1,129 @@ +# Feature: Contact Detail Page — UID statt DB-ID + +## Status + +**Geplant (Backlog)** — Kein aktueller Bedarf, aber sinnvolles Zukunftsfuture. + +## Ausgangslage + +Die Contact-Detail-Seite wird aktuell über die interne Datenbank-ID (`id INT AUTO_INCREMENT`) aufgerufen: + +``` +/contacts/{contact_id} (HTML) +/api/contacts/{contact_id} (JSON) +``` + +Die `id` kann sich bei einem Re-Sync (z.B. nach Datenverlust) ändern. Die **UID** aus CardDAV ist hingegen stabil und bleibt über Synchronisationen hinweg gleich. + +## Problem + +- Die `id` ist kein stabiler Identifier — sie kann sich ändern +- Bei einem Re-Sync mit neuem Schema werden alle alten IDs ungültig +- Geteilte Links oder Lesezeichen auf `/contacts/42` brechen + +## Lösungsidee + +Den Lookup von `WHERE id = %s` auf `WHERE uid = %s` umstellen. + +### Einschränkung + +UIDs sind pro Account eindeutig (`UNIQUE KEY (account, uid)`), nicht global. Für Admin-Nutzer mit `show_all=True` müsste der Account ebenfalls im Lookup berücksichtigt werden. + +### Mögliche URL-Patterns + +| Ansatz | URL | Vorteil | Nachteil | +|--------|-----|---------|----------| +| UID only | `/contacts/{uid}` | Einfach | Nur sicher, wenn UID global eindeutig | +| Account + UID | `/contacts/{account}/{uid}` | Explizit, immer korrekt | Längere URL, Admin-Modus nötig | +| Komposit | `/contacts/{account}--{uid}` | Ein Path-Parameter | Unschön | + +**Empfehlung:** Account + UID (`/contacts/{account}/{uid}`) — sauber und eindeutig. + +## Betroffene Dateien + +### 1. `src/api/main.py` — API-Routen + +**JSON API (Zeile 127-149):** +- Route: `GET /api/contacts/{contact_id}` → `GET /api/contacts/{account}/{contact_uid}` +- Parameter: `contact_id: int` → `account: str, contact_uid: str` +- Query: `WHERE id = %s` → `WHERE uid = %s AND account = %s` + +**HTML Detail (Zeile 578-601):** +- Route: `GET /contacts/{contact_id}` → `GET /contacts/{account}/{contact_uid}` +- Parameter: `contact_id: int` → `account: str, contact_uid: str` +- Query: `WHERE id = %s` → `WHERE uid = %s AND account = %s` + +### 2. `src/api/templates/index.html` — Kontaktliste + +**Zeile 193:** +```html + + + + + +``` + +### 3. `src/api/templates/dashboard.html` — Geburtstagsliste + +**Zeile 309:** +```html + + + + + +``` + +### 4. `src/mailer.py` — Birthday-Mails + +**Zeile 25:** SELECT um `uid` erweitern (oder `id` entfernen): +```sql +SELECT account, uid, full_name, ... +``` + +**Zeile 84 (Plain Text):** +```python +# Aktuell +line += f" → {Config.WEB_URL.rstrip('/')}/contacts/{b['id']}" +# Neu +line += f" → {Config.WEB_URL.rstrip('/')}/contacts/{b['account']}/{b['uid']}" +``` + +**Zeile 95 (HTML):** +```python +# Aktuell +contact_link = f"{Config.WEB_URL.rstrip('/')}/contacts/{b['id']}" +# Neu +contact_link = f"{Config.WEB_URL.rstrip('/')}/contacts/{b['account']}/{b['uid']}" +``` + +### 5. `src/api/schemas.py` — Pydantic Models + +- `ContactOut.id` (Zeile 9): Kann bleiben (nützlich für interne Zwecke), aber `uid` wird primärer Lookup-Key +- `GroupMemberOut.id` (Zeile 53): Prüfen ob noch benötigt + +### 6. `src/db.py` — DB-Queries + +SELECT-Klauseln enthalten bereits `uid`. `id` kann aus SELECTs entfernt werden, wo es nicht gebraucht wird. + +### 7. Dokumentation + +- `README.md` (Zeilen 166, 244, 247): Endpoint-Pattern aktualisieren +- `SPEC.md` (Zeilen 253, 256): Endpoint-Pattern aktualisieren + +## Risiken + +1. **Backward-Kompatibilität:** Bestehende URLs (`/contacts/42`) brechen. Kein eleganter Redirect möglich (alte ID sagt nichts über UID aus). +2. **UID-Encoding:** UIDs aus CardDAV können Sonderzeichen enthalten — `urlencode` in Templates ist Pflicht. +3. **Admin-Modus:** Bei `show_all=True` ist `account_name` aktuell `None`. Der neue Ansatz mit explizitem Account-Pfad löst das sauber auf. + +## Umsetzungsreihenfolge + +1. `src/api/main.py` — Routen ändern (API + HTML) +2. `src/api/templates/index.html` — Links anpassen +3. `src/api/templates/dashboard.html` — Links anpassen +4. `src/mailer.py` — URL-Bau anpassen +5. `src/api/schemas.py` — `GroupMemberOut.id` prüfen +6. `README.md` + `SPEC.md` — Dokumentation aktualisieren +7. `ruff check src/` — Lint-Check