My-Weekly-ToDo-List/TODO.md
mARTin-B78 6fe4b69514
Some checks are pending
Playwright Tests / test (push) Waiting to run
Test and SonarQube / quality (push) Waiting to run
feat: Heute-Hervorhebung im Spaltenkopf, Termin-Symbole nach oben rechts
Zwei UI-Wuensche aus der Wochenansicht (TODO.md Block I).

Der Spaltenkopf ist position: sticky und deckte mit --weekly-bg ab -- die
Hervorhebung des heutigen Tages begann daher erst unterhalb von Wochentag
und Datum. Jetzt faerbt sich der Kopf mit.

Die Symbole sitzen statt unten rechts oben rechts. Damit Titel und Uhrzeit
nicht darunter laufen, bekommen sie rechts Freiraum -- ueber
:has(.event-icons-row) nur dort, wo ueberhaupt Symbole erscheinen. Fuer
beide Darstellungen umgesetzt: Zeitraster und Liste.

Verifiziert: npm run build Exit 0. Das Ergebnis ist rein visuell und daher
nur im Browser wirklich pruefbar.

v1.119.0

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 14:08:23 +02:00

367 lines
19 KiB
Markdown

# To-do — Repository-Audit vom 2026-09-06
Abarbeitungsliste aus dem Repository-Audit. Reihenfolge ist bewusst gewählt:
schnelle Sicherheitsgewinne zuerst, das große Refactoring zuletzt.
Abhaken mit `[x]`. Die Nummern in Klammern entsprechen den Punkten aus dem Audit.
---
## Block A — Schnell erledigt, sofortiger Gewinn
### A1. Tote Mock-Login-Route entfernen (Audit 3)
- [x] `src/app/api/auth/login/route.ts` löschen
- [x] Prüfen, dass niemand sie aufruft (`grep -rn "api/auth/login" src/`)
- [ ] Commit + Version bumpen
**Warum:** Altcode aus der Anfangszeit. Vergleicht gegen ein hartcodiertes Fake-Passwort
(`$2b$10$examplehashedpassword`), erzeugt ein bedeutungsloses `mock-jwt-token-…` und setzt es
als Cookie. Weil die Middleware `/api/auth/*` bewusst durchlässt, ist die Route **ohne Login
von außen erreichbar** und schreibt jede probierte E-Mail per `console.log` ins Log.
Kein Login-Bypass (der Vergleich schlägt immer fehl), aber unnötige Angriffsfläche.
### A2. `npm test` reparieren (Audit 6)
- [x] Entscheiden: Jest raus oder Jest einrichten
- [x] `package.json`: `"test"` auf das zeigen lassen, was wirklich existiert
- [x] `"test:e2e"` für Playwright ergänzen
- [x] Debug-Skripte in `tests/` aussortieren
- [x] **63 Unit-Tests** für die ausgelagerten reinen Funktionen (`tests/unit/`), laufen in
~170 ms ohne Browser und ohne Datenbank über Nodes eingebauten Test-Runner
- [ ] E2E-Tests mit echtem Login schreiben (braucht laufende App + Datenbank)
- [ ] Testlauf in den nächtlichen Qualitätslauf aufnehmen (`scripts/quality-check.sh`)
**Warum:** `"test": "jest"` — aber es gibt **keine Jest-Config und keinen einzigen Jest-Test**.
Die 15 Dateien in `tests/` sind Playwright-Specs, und die Namen verraten den Charakter:
`login-debug.spec.ts`, `css-debug-test.spec.ts`, `login-simple-debug.spec.ts`. Wegwerf-Skripte,
keine Regressionstests. `npm test` läuft ins Leere.
**Aufräumen:** Die reinen Debug-Skripte löschen, die brauchbaren behalten und benennen.
### A3. `dev`-Branch auflösen (Audit 5)
- [ ] Die vier Maßnahmen aus `f6c4a21` frisch auf `main` anwenden (siehe A1, B1, B2)
- [ ] `dev` auf `main` zurücksetzen **oder** löschen
- [ ] `CLAUDE.md`: Branch-Strategie an die gelebte Praxis anpassen
**Warum:** Commit `f6c4a21` vom 04.07.2026 behebt bereits Rate-Limiting, Token-Hashing,
die Mock-Route und die Secret-Datei — wurde aber nie gemerged. `dev` fehlen inzwischen
**70 Commits** von `main`, ein Merge würde alte Dateistände zurückholen. Deshalb:
Commit als *Vorlage* nehmen, Änderungen neu anwenden, `dev` danach entsorgen.
`CLAUDE.md` beschreibt einen `dev` → `main`-Workflow, der faktisch nicht gelebt wird
(alles läuft direkt auf `main`). Doku an die Realität anpassen ist ehrlicher.
---
## Block B — Sicherheit
### B1. Rate-Limiting auf den Auth-Endpunkten (Audit 4)
- [x] Sliding-Window-Limiter in `src/lib/rate-limit.ts` anlegen
- [x] Einbinden in `signup`
- [x] Einbinden in `resend-verification`
- [x] Einbinden in `reset-password`
- [x] Einbinden in `verify-email`
- [x] Limits dokumentieren (Vorschlag: 5 Versuche / 15 Minuten je IP)
**Warum:** Im gesamten `src/` ist **kein Limiter** vorhanden. Signup, Passwort-Reset und
Verification-Mails sind unbegrenzt aufrufbar — Einladung zum Missbrauch deines SMTP-Zugangs.
### B2. Passwort-Reset-Token nur als SHA-256-Hash speichern (Audit 4)
- [x] Beim Erzeugen: Klartext per Mail, **Hash** in die DB
- [x] Beim Einlösen: eingehenden Token hashen, dann vergleichen
- [ ] Migration für bestehende Tokens (einfachster Weg: alle invalidieren)
**Warum:** `src/app/api/auth/reset-password/route.ts:28` legt `passwordResetToken` im
**Klartext** ab. Wer Lesezugriff auf die Datenbank hat, kann fremde Konten übernehmen.
Der Token selbst kommt aus `crypto.randomUUID()` — das ist in Ordnung, nur die Ablage nicht.
### B3. Secrets aus dem Git entfernen und rotieren (Audit 2)
- [x] `.env.production.save` und `.env.local` gelöscht (mit `shred` überschrieben)
- [x] `.gitignore` auf `.env*` erweitert (Ausnahme `.env.example`)
- [x] `NEXTAUTH_SECRET` neu erzeugt — 44 Zeichen, geprüft
- [x] `SMTP_PASSWORD` neu gesetzt
- [x] `deployment-notes.md` und `INFRASTRUCTURE_REQUIREMENTS.md` aus dem Index entfernt
- [x] Klartext-Werte in beiden Dateien durch Platzhalter ersetzt
- [x] Passwort aus `tests/debug/db-connection-test.spec.ts` entfernt
- [x] Nach dem Wechsel neu eingeloggt
- [ ] Entscheiden: Historie umschreiben oder belassen (Empfehlung: belassen)
- [ ] Prüfen, ob eines der geleakten DB-Passwörter noch aktiv ist (siehe F1)
**Warum:** `.env.production.save` ist eingecheckt und enthält **echte Werte** —
`DATABASE_URL` (70 Zeichen), `NEXTAUTH_SECRET` (20 Zeichen), `SMTP_PASSWORD` (20 Zeichen).
`.gitignore` listet `.env.production`, die `.save`-Endung fällt durchs Raster.
Drin seit Commit `da8d019`.
**Entwarnung:** Das GitHub-Repo ist **privat** — nicht öffentlich abgreifbar. Die Werte
stehen aber in der kompletten Historie, in jedem Klon und auf Gitea.
Anleitung siehe [`docs/secret-rotation.md`](docs/secret-rotation.md).
---
## Block C — Build und Typsicherheit
### C1. Build darf Fehler nicht mehr verschlucken (Audit 1)
- [x] **Schritt 1:** `eslint.ignoreDuringBuilds` in `next.config.js` auf `false`
- [x] Gemeldete ESLint-Fehler abarbeiten — es waren nur **2** (`prefer-const` in
`SettingsSidebar.tsx`), verifiziert: `next lint` endet jetzt mit Exit 0
- [x] **Schritt 2:** `npm run type-check` — es waren **7 Fehler**, alle behoben
- [x] **Schritt 3:** `typescript.ignoreBuildErrors` auf `false` — Build prüft jetzt Typen
- [x] `tsconfig.json`: `include` von `**/*` auf `src/**/*` eingegrenzt. **Das war die
Ursache der Langsamkeit**, nicht die Dateigröße: Der Lauf dauert jetzt **19 Sekunden**
statt über 30 Minuten. Meine gegenteilige Vermutung war falsch.
- [x] **3 Dateien mit `@ts-nocheck`** entschärft — im Projekt gibt es jetzt **kein einziges
`@ts-nocheck` mehr**. `calendar-cache.ts` brauchte es gar nicht (0 Fehler), in
`microsoft-todo.ts` fehlten Typannotationen, und in `outlook-calendar.ts` steckte
ein möglicher Laufzeitfehler dahinter.
- [ ] `any`-Stellen reduzieren — ESLint zählt **470** `no-explicit-any` — fortlaufend, kein Blocker
- [ ] **209** ungenutzte Variablen (`no-unused-vars`) aufräumen — fortlaufend
- [ ] **11** unvollständige Hook-Abhängigkeiten (`react-hooks/exhaustive-deps`) prüfen —
die sind die gefährlichsten der Warnungen, weil daraus echte Bugs entstehen
**Warum:** `next.config.js` hat `ignoreDuringBuilds: true` **und** `ignoreBuildErrors: true`.
`tsconfig.json` sagt zwar `strict: true`, das ist aber wirkungslos, weil der Build die Fehler
gar nicht meldet. **Ein Typfehler fällt nicht beim Bauen auf, sondern erst als kaputte Seite
im Browser.** Ein erfolgreicher Build ist derzeit *kein* Beweis, dass der Code korrekt ist.
**Achtung Reihenfolge:** Erst ESLint, dann TypeScript. Beides gleichzeitig abzuschalten
blockiert jeden Deploy, bis alles behoben ist.
---
## Block D — Struktur
### D1. Dateien über 4000 Zeilen aufteilen (Audit 7)
Betroffen sind genau zwei Dateien:
| Datei | Zeilen |
|---|---:|
| `src/components/WeeklyView.tsx` | 11.556 *(war 11.906)* |
| `src/components/SettingsSidebar.tsx` | 5.445 |
**Voraussetzung:** C1 muss laufen — ohne Typecheck ist ein Refactoring dieser Größe
Blindflug. Ebenso hilfreich: A2, damit es überhaupt Tests gibt.
#### WeeklyView.tsx → Zielstruktur
- [x] Typen nach `src/lib/weekly/types.ts` (7 Typen, 97 Zeilen) — Build verifiziert
- [ ] `src/components/weekly/` anlegen
- [x] Datums-/Zeitraster-Helfer nach `src/lib/weekly/date-utils.ts` (13 Funktionen, 202 Zeilen)
- [x] Cookie-/Geräteeinstellungen nach `src/lib/weekly/device-settings.ts`
- [x] Formatierungs-Helfer nach `src/lib/weekly/format.ts`
- [ ] `getWeekNumber` existiert **dreimal** im Projekt (WeeklyView, `WeekPrintModal.tsx`,
`api/user/export-week-view/route.ts`) — Duplikate gegen `date-utils.ts` ersetzen,
vorher die drei Implementierungen auf Gleichheit prüfen
- [ ] `calendar-cache.ts` hat mit `getWeekStart` eine **vierte** eigene Wochenlogik —
ebenfalls gegen `date-utils.ts` ersetzen
- [ ] Drag & Drop in einen Hook `useTaskDragDrop.ts`
- [ ] Someday-Listen in `SomedayLists.tsx`
- [ ] All-Day-Leiste in `AllDayBar.tsx`
- [ ] Zeitraster + Tagesspalten in `TimeGrid.tsx` / `DayColumn.tsx`
- [ ] Einstellungs-Flyouts in `QuickSettingsPanel.tsx`
- [ ] Daten-Laden in `useWeeklyData.ts`
- [ ] `WeeklyView.tsx` bleibt als Orchestrator übrig
#### SettingsSidebar.tsx → Zielstruktur
- [ ] `src/components/settings/` anlegen
- [ ] Ein Panel pro Themenbereich als eigene Datei
- [ ] Gemeinsame Bausteine (Farbwähler, Schalterzeile) in `settings/controls/`
**Vorgehen:** In kleinen Schritten, jeder Schritt ein eigener Commit, nach jedem Schritt
`npm run build`. Kein "großer Wurf" — die Datei ist zu zentral.
**Zwischenstand nach drei Schnitten:** 11.906 → 11.556 Zeilen, also **rund 3 %**. Damit sind
die reinen Funktionen ohne Komponenten-State weitgehend heraus — das war der leichte Teil.
Die verbleibenden ~11.500 Zeilen sind die Komponente selbst: JSX und Event-Handler, die auf
gemeinsamen State zugreifen. Jeder weitere Schnitt braucht echtes Props-Design statt
Verschieben. Realistisch pro Schnitt ein eigener Durchgang, und **erst mit Tests (A2) oder
zumindest laufendem Typecheck sinnvoll** — ein Fehler beim Herauslösen von Drag & Drop fällt
sonst wieder erst im Browser auf.
**Warum:** `WeeklyView.tsx` ist ein Viertel des gesamten Quellcodes. Der Weekday-Bug von
Anfang September saß genau dort und war nur deshalb schwer zu finden.
---
## Block E — Dauerhafte Absicherung
### E1. Regelmäßige automatische Prüfung (Audit 8)
- [x] SonarQube-Analyse gegen https://sq.martin-bierschenk.de einrichten
- [x] Regelmäßigen Lauf aufsetzen — nightly 03:00, `todo-sonar.timer` (systemd user unit)
- [x] `npm audit` in denselben Lauf aufnehmen (`scripts/quality-check.sh`)
- [ ] `todo-sonar.timer` auf `scripts/quality-check.sh` umstellen statt nur `todo-sonar-scan`
- [ ] Playwright-Tests in den nächtlichen Lauf aufnehmen (braucht laufende App — erst nach A2)
- [ ] Quality Gate im SonarQube definieren
- [ ] Ergebnis irgendwohin melden, wo es auffällt (bisher nur Konsolenausgabe)
- [ ] **Entscheiden:** `.github/workflows/sonarqube.yml` löschen oder behalten
**Stand:** Die Analyse läuft bereits — nächtlich um 03:00 auf dem Spark, per systemd-Timer,
Ergebnis auf https://sq.martin-bierschenk.de. Was fehlte, war `npm audit` (Sicherheitslücken
in Abhängigkeiten findet SonarQube nicht) und ein Bündel-Skript. Beides ist jetzt da.
**Zur Entscheidung:** GitHub Actions wurde am 02.09. bewusst verworfen — das Repo ist privat,
Actions-Minuten kosten, und der SonarQube-Server läuft ohnehin selbst gehostet. Trotzdem liegt
seit dem 03.09. ein Workflow im Repo, der bei jedem Push auf `main` anspringt. Er ruft
`npm run type-check` und `npm run lint` auf — beides schlägt derzeit fehl (siehe C1), der
Workflow wäre also dauerhaft rot. Sein Projekt-Key war zudem falsch und hätte in ein zweites,
leeres SonarQube-Projekt geschrieben; das ist korrigiert. Entweder löschen (passt zur
Entscheidung vom 02.09.) oder bewusst behalten und C1 vorziehen.
**Warum:** Bisher fallen Regressionen erst in Produktion auf. In Kombination mit C1
(Build meldet Fehler) und A2 (Tests laufen) entsteht daraus ein echtes Netz.
### E2. Abhängigkeiten auf Sicherheitslücken prüfen
- [ ] `npm audit` auf dem Container ausführen
- [ ] Kritische Funde beheben
**Warum:** Konnte im Audit **nicht** geprüft werden — `node_modules` ist auf dem
CIFS-Share nicht installierbar (keine Symlinks). Zu bekannten Lücken in den
Abhängigkeiten liegt daher **keine Aussage** vor. Muss nachgeholt werden.
---
## Block F — Zurückgestellt
### F1. Datenbank-Passwort rotieren *(bewusst nach hinten geschoben)*
- [ ] Prüfen, ob eines der geleakten Passwörter noch aktiv ist
- [ ] Falls ja: rotieren nach [`docs/secret-rotation.md`](docs/secret-rotation.md), Schritt 2
- [ ] Falls nein: nichts zu tun, Haken dran
**Warum zurückgestellt:** Von den drei Zugangsdaten ist dieses das am wenigsten dringende.
Die Datenbank hört auf einer **privaten LAN-Adresse**, nicht am Internet. Wer das Passwort
aus dem Git holen will, braucht Zugriff auf das **private** Repository — und ist damit
ohnehin schon im Netz. `NEXTAUTH_SECRET` und `SMTP_PASSWORD` waren die kritischen, beide
sind erledigt.
**Und es ist riskant:** Supabase läuft selbst gehostet per Docker. Dasselbe Passwort steckt
in `supabase/docker/.env` **und** in Postgres. Ein halb durchgeführter Wechsel legt die
Datenbank und damit die App lahm. Das gehört an einen ruhigen Abend, nicht nebenbei.
**Zwei verschiedene Passwörter liegen im Git** (Fingerabdrücke, keine Klartexte):
| Fingerabdruck | Wo | Stand |
|---|---|---|
| `59fb583eaae9` | ehem. `deployment-notes.md`, `INFRASTRUCTURE_REQUIREMENTS.md` | altes Docker-Compose-Setup (`@db:5432`); Dateien bereinigt |
| `9dbb40b14c4d` | ehem. `.env.production.save` | Supabase-Zugang, **Status offen** — Datei gelöscht, Wert steht noch in der Historie |
Vom Nutzer bestätigt: das alte `NEXTAUTH_SECRET` (20 Zeichen) und das alte `SMTP_PASSWORD`
sind **nicht mehr in Verwendung**, beide wurden rotiert. Offen ist allein das
Datenbank-Passwort.
Prüfen lässt sich das auf dem Container, ohne einen Wert auszugeben:
```bash
cd /root/My-Weekly-ToDo-List
grep '^DATABASE_URL=' .env | sed -E 's|.*://[^:]+:([^@]+)@.*|\1|' | tr -d '\n' | sha256sum | cut -c1-12
```
Ergibt der Befehl `9dbb40b14c4d`, ist das geleakte Passwort noch in Benutzung — dann lohnt
F1. Kommt etwas anderes heraus, wurde es längst getauscht und du kannst F1 abhaken.
---
## Block H — Aus dem scharfen Typecheck
### H1. Notion-Anbindung war defekt *(migriert, aber ungetestet)*
- [ ] Migration praktisch prüfen (braucht Notion-Zugang)
- [ ] Betroffene Nutzer informieren: Notion-Verbindung muss neu eingerichtet werden
**Warum:** Der Code rief `notion.databases.query()` auf. Die Methode gibt es in
`@notionhq/client` v5 nicht mehr (nur noch retrieve/create/update) — der Aufruf lief zur
Laufzeit in ein "is not a function". **Die Notion-Anbindung war damit schlicht kaputt**, und
das ist niemandem aufgefallen, weil der Build Typfehler verschluckte.
Umgestellt auf `dataSources.query()`; die Suche filtert jetzt auf `data_source` statt
`database`. Das ist in sich konsistent, aber **mangels Notion-Zugang nicht erprobt**.
⚠️ **Gespeicherte Verbindungen enthalten alte `database_id`s. Das sind keine
`data_source_id`s** — betroffene Nutzer müssen Notion einmal neu verbinden.
---
## Block I — UI-Wünsche
### I1. Aktuellen Tag auch im Spaltenkopf hervorheben *(erledigt, bitte ansehen)*
- [x] Hervorhebungsfarbe zusätzlich hinter Wochentagsname und Datum gelegt
**Wunsch:** Der laufende Tag soll deutlicher auffallen. Aktuell ist nur der Spaltenkörper
eingefärbt, der Kopfbereich mit "SEP 7 / MONDAY" bleibt weiß.
**Wo:** `globals.css:666` färbt `.weekly-day-column.is-today` mit
`var(--weekly-today-highlight)`. Der Kopf ist ein eigenes `<header class="weekly-day-header">`
([WeeklyView.tsx:7089](src/components/WeeklyView.tsx#L7089)) und übermalt die Farbe
offenbar mit eigenem Hintergrund. Der Wochentagsname hat bereits eine eigene Regel
(`globals.css:708`, `.weekly-day-name.is-today` setzt nur die Schriftfarbe).
**Zu klären:** Der Kopf ist vermutlich `sticky` und braucht deshalb einen deckenden
Hintergrund — die Farbe muss also gesetzt, nicht nur geerbt werden.
### I2. Symbole in Kalenderterminen nach oben rechts *(erledigt, bitte ansehen)*
- [x] `.event-icons-row` nach oben rechts verschoben, Titel bekommt rechts Freiraum
**Wunsch:** Die kleinen Symbole (Wiederholung, Anbieter) sitzen unten rechts im Termin und
sollen nach oben rechts.
**Wo:** `globals.css:3183` — `.event-icons-row` hat `bottom: 2px; right: 4px;`. Für oben
rechts wird daraus `top: 2px; right: 4px;`.
**Achtung:** Der Titel beginnt oben links; bei kurzen Terminen könnten sich Symbole und
Titel überlappen. Vermutlich braucht die Titelzeile rechts etwas Freiraum
(`padding-right`), damit nichts verdeckt wird.
---
## Block G — Infrastruktur (Supabase-LXC 201)
### G1. Container läuft privilegiert *(Sicherheitsthema)*
- [ ] Entscheiden, ob `unprivileged: 0` so bleiben soll
- [ ] Falls nicht: Container neu aufsetzen und Daten übernehmen
**Warum:** `pct config 201` meldet `unprivileged: 0`. Wer aus diesem Container ausbricht, ist
**root auf dem NUC** — bei einem Container, der die Datenbank betreibt, die relevantere
Baustelle als ein fehlendes Root-Passwort. Meist wird das umgestellt, weil Docker im
unprivilegierten Container zickt; nachvollziehbar, aber ein bewusster Kompromiss.
### G2. Besitzverhältnisse im Dateisystem sind schief
- [ ] Kurzfix, falls SSH-Zugang zum Container gebraucht wird:
`pct exec 201 -- chown 0:0 /etc/passwd` und `pct exec 201 -- chown 0:42 /etc/shadow`
- [ ] Langfristig: Container sauber neu aufsetzen
**Warum:** Das Dateisystem stammt aus einem **unprivilegierten** Container (alles liegt mit
UID 100000 auf der Platte), läuft aber jetzt **privilegiert**, also ohne UID-Mapping. root
besitzt dadurch praktisch keine Datei im System. Zeitstempel belegen es: alles vom 26.02.2026
mit UID 100000, nur `/etc/hostname` vom 06.09. mit `0:0` — das schreibt Proxmox von außen.
Symptom war `passwd: Authentication token manipulation error`. Das System läuft trotzdem,
weil root per `CAP_DAC_OVERRIDE` die Rechteprüfung überspringt; Werkzeuge wie `passwd`, die
strenger prüfen, fallen darüber. Weitere solche Merkwürdigkeiten sind zu erwarten.
⚠️ **Kein `chown -R` über das ganze Dateisystem** — nicht am laufenden Container und schon
gar nicht mit Docker darin. Postgres und die Supabase-Container haben eigene UIDs (999, 70, …),
die dabei zerschossen würden.
**Kein Blocker:** `pct enter 201` gibt jederzeit eine Root-Shell ohne Passwort. Ein
Root-Passwort wird nur für direkten SSH-Zugang gebraucht.
### G3. Supabase Studio ist nicht erreichbar
- [ ] Auf 201 prüfen: `docker compose ps` — laufen die Container?
- [ ] Port klären: Studio hängt hinter Kong auf **8000**, nicht 8001
- [ ] `scripts/supabase-setup.sh` korrigieren: falsche Port-Ausgabe und Shebang `#!/bash`
**Warum:** `http://<IP>:8001` antwortet nicht. Die Zahl stammt aus einer `echo`-Zeile in
`scripts/supabase-setup.sh`, die nirgends eine Portfreigabe setzt — beim offiziellen
`supabase/docker`-Stack ist Studio nur über Kong auf 8000 erreichbar. Zusätzlich hat das
Skript `#!/bash` statt `#!/bin/bash` und ist so gar nicht direkt ausführbar.
---
## Kleinkram (ohne Eile)
- [ ] 114 `console.log` aus dem Produktionscode entfernen (die 275 `console.error` sind in Ordnung)
- [ ] `NEXTAUTH_SECRET` auf 32 Byte verlängern (erledigt sich mit B3)
- [ ] API-Routen: JSON-401 statt HTML-Redirect für nicht angemeldete Aufrufe
- [ ] `deploy.sh` und `docker-compose.yml` löschen oder als veraltet markieren — sie
beschreiben ein Portainer-Setup, das nicht mehr verwendet wird
- [ ] Eigener Auth-Check in `apple/sync` und `weather/geocode` (aktuell nur durch die
Middleware geschützt — zusätzliche Absicherung schadet nicht)