# PROJ-53: Konfigurierbare Listenanzahl pro Seite

## Status: Deployed
**Created:** 2026-06-14
**Last Updated:** 2026-06-14

## Dependencies
- PROJ-25 (User-Profil & Einstellungen) — neue Einstellung erscheint auf `/settings`
- PROJ-6 (Volltext-Suche & Filterung) — Mail-Listenansicht nutzt die Einstellung

## User Stories
- Als Benutzer möchte ich in meinem Profil festlegen können, wie viele E-Mails pro Seite in der Listenansicht (Suche/Archiv) angezeigt werden, damit ich die Ansicht an meine Bildschirmgröße bzw. meine Vorlieben anpassen kann.

## Acceptance Criteria
- [ ] Im Usermenü unter "Profil & Einstellungen" gibt es ein neues Auswahlfeld "Einträge pro Seite" mit den Optionen 25 / 50 / 100 / 200 (Default: 25)
- [ ] Die Auswahl wird über `PATCH /api/auth/preferences` gespeichert (persistiert in `users.list_page_size`)
- [ ] Beim Laden der Mail-Listenansicht (`/search`) wird die gespeicherte Seitengröße verwendet (statt der festen `PAGE_SIZE = 25`)
- [ ] Bestehende Nutzer ohne gespeicherten Wert erhalten weiterhin 25 (Default)
- [ ] Pagination ("Zurück"/"Weiter", "Seite X von Y") funktioniert korrekt mit allen Werten

## Edge Cases
- Nutzer ändert die Einstellung während eine Suche aktiv ist → nächste Suche/Seite-1-Reload verwendet neuen Wert
- Ungültiger/manipulierter Wert im Request (außerhalb 25/50/100/200) → Backend lehnt ab oder fällt auf Default zurück
- Nutzer ohne gesetzten Wert (bestehende Accounts, Spalte NULL) → Default 25

## Technical Requirements (optional)
- Persistenz: neue Spalte `users.list_page_size` (INT, Default 25)
- Security: Endpoint erfordert eingeloggte Session (analog `PATCH /api/auth/email`)

---

## Tech Design (Solution Architect)
_Übersprungen auf Wunsch des Nutzers — direkte Umsetzung._

Kurzentscheidung (statt vollem Architektur-Dokument):
- Neue Spalte `users.list_page_size INT DEFAULT 25` via `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` in `internal/userstore/userstore.go`
- Neuer Endpoint `PATCH /api/auth/preferences` (Body: `{"list_page_size": 25|50|100|200}`), serverseitige Validierung gegen erlaubte Werte
- `GET /api/auth/me` (bzw. Session-Response) liefert `list_page_size` mit
- Frontend: `src/app/settings/page.tsx` neues Select-Feld; `src/app/search/page.tsx` verwendet `user.list_page_size ?? 25` statt Konstante `PAGE_SIZE`

## QA Test Results
**Tested:** 2026-06-14 (Code-Review + Logikprüfung, kein lokaler Build/Live-Test möglich)
**Methode:** Statische Code-Analyse Backend (Go) + Frontend (TS/React) gegen Acceptance Criteria. Datenfluss login/me → Cache → search-Page nachverfolgt.

### Acceptance Criteria
| # | Kriterium | Status | Anmerkung |
|---|-----------|--------|-----------|
| 1 | Select "Einträge pro Seite" 25/50/100/200, Default 25 | PASS | `settings/page.tsx` Card "Listenansicht", Select mit 4 Optionen; Init aus `user.list_page_size \|\| 25` |
| 2 | Speichern via `PATCH /api/auth/preferences` → `users.list_page_size` | PASS | Handler + `UpdateListPageSize` + Route korrekt; serverseitige Whitelist-Validierung |
| 3 | `/search` nutzt gespeicherte Seitengröße statt fester PAGE_SIZE | PASS (mit Vorbehalt B-3) | `pageSize = user?.list_page_size ?? DEFAULT_PAGE_SIZE`, in allen 3 Suchpfaden + totalPages verwendet |
| 4 | Bestehende Nutzer ohne Wert → Default 25 | PASS | DB-Spalte `NOT NULL DEFAULT 25`; FE-Fallback `?? 25` |
| 5 | Pagination funktioniert mit allen Werten | PASS | `totalPages = ceil(total/pageSize)`, Buttons disabled-Logik korrekt |

### Bugs

**BUG-1 (Critical) — Backend kompiliert nicht: `s.db.Exec` undefiniert**
- Datei: `internal/userstore/userstore.go:112`
- `Store` hat nur das Feld `pool` (Zeile 60), aber der PROJ-46-Block ruft `s.db.Exec(...)` auf. `s.db` existiert nicht → `go build` schlägt fehl.
- Auswirkung: Gesamtes Backend baut nicht, PROJ-53 ist damit nicht deploybar/testbar. Pre-existing (uncommitteter PROJ-46-Block), aber blockiert dieses Feature.
- Reproduktion: `CGO_ENABLED=0 go build ./cmd/archivmail/` → Compile-Error `s.db undefined (type *Store has no field or method db)`.
- Fix: `s.db.Exec` → `s.pool.Exec`.
- Priorität: P0 (Blocker, vor jedem Deploy).

**BUG-2 (Low) — Keine Audit-Log-Eintragung für Preference-Änderung**
- Datei: `internal/api/profile_handlers.go:160`, `handleChangePreferences`.
- `handleChangePassword`/`handleChangeEmail` loggen via `s.audlog.Log`, `handleChangePreferences` nicht. Im Spec/Impl-Notes bewusst so entschieden ("kleine Präferenz"). Konsistent mit Projektregel "alle schreibenden Operationen loggen"? Grenzfall — dokumentiert, kein Blocker.
- Priorität: P3 (akzeptabel, da bewusste Designentscheidung).

**BUG-3 (Low/UX) — Seitengrößen-Änderung resettet Suche/Seite nicht sofort, nur über useEffect-Reload**
- Datei: `src/app/search/page.tsx:172-187`.
- Ändert der User die Seitengröße in `/settings`, ruft `refresh()` den Cache neu. Auf `/search` triggert der `[user, pageSize]`-useEffect dann einen Reload auf **Seite 1, ohne aktive Filter** (der Effekt ruft `searchEmails({page:1,...})` ohne query/from/to). War der User mitten in einer gefilterten Suche, gehen die Filter beim erneuten Mount verloren. In der Praxis erfolgt die Änderung auf einer anderen Route (`/settings`), beim Zurücknavigieren ist Reset auf Seite 1 akzeptabel und entspricht Edge-Case-Doku ("nächste Suche/Seite-1-Reload"). Kein Blocker.
- Priorität: P3.

**HINWEIS (Type) — `MeResponse` enthält kein `id`/`active`, `useAuth().user` ist `MeResponse`**
- `src/app/search/page.tsx` greift nur auf `user.list_page_size`, `user.role`, `user.username` zu — alle in `MeResponse` vorhanden. Kein Typfehler. OK.

### Security-Audit
- **Validierung Manipulation (Edge Case):** `allowedListPageSizes` Whitelist im Handler → ungültige Werte (0, 999, negativ, 1000000) ergeben 400. PASS. Kein Risiko für Resource-Exhaustion über überhöhte page_size, da Backend nur Whitelist-Werte akzeptiert. ABER: prüfen, ob der `/api/search`-Handler `page_size` aus dem Request **selbst** ebenfalls cappt — der FE sendet zwar nur Whitelist-Werte, aber ein direkter API-Aufruf mit `page_size=1000000` könnte ungekappt durchgehen (nicht Teil von PROJ-53, aber relevant). EMPFEHLUNG: serverseitiges Cap im Search-Handler verifizieren.
- **Auth:** `PATCH /api/auth/preferences` ist via `s.auth(...)` geschützt; Handler prüft zusätzlich `sess.UserID == 0`. Update nur für eigene `user.ID` (aus Session-Username geladen) — keine IDOR. PASS.
- **Tenant-Isolation:** Nicht betroffen (rein UI-Präferenz pro User). PASS.

### Regression
- `scanUser`/`scanUserRow`/`VerifyPassword`/`UpsertLDAPUser`/alle SELECTs konsistent um `list_page_size` erweitert — keine Spaltenanzahl-Mismatches gefunden. PASS.
- login/me-Response um Feld erweitert, additiv — keine Breaking-Changes für bestehende FE-Konsumenten.

### Production-Ready: NEIN
Blocker BUG-1 (Backend kompiliert nicht) muss vor Deploy gefixt werden (Backend Developer). Danach Live-Smoke-Test auf 192.168.1.132 empfohlen: Migration prüfen (`\d users`), `PATCH /api/auth/preferences` mit gültigem/ungültigem Wert, Pagination mit 200 Einträgen.

### Fixes nach QA (2026-06-14)
- **BUG-1 (Critical, P0) — FIXED:** `internal/userstore/userstore.go:112` — `s.db.Exec` → `s.pool.Exec` (PROJ-46-Block). Backend kompiliert wieder.
- **Security-Empfehlung — FIXED:** `internal/api/search_handlers.go` — `page_size`-Query-Parameter wird jetzt serverseitig auf max. 500 gecappt (Resource-Exhaustion-Schutz, unabhängig vom Frontend-Whitelist 25/50/100/200).
- BUG-2 (kein Audit-Log) und BUG-3 (Filter-Reset bei Settings-Wechsel) bleiben als akzeptierte Designentscheidungen (P3, kein Blocker).

## Deployment
**Deployed:** 2026-06-14
- Test: 192.168.1.132 — via `update.sh` (commit 4c20a00, dann 776dee8 für PROJ-54), Backend+Frontend laufen, Manticore-Reindex 16177/16177 (0 Fehler), Backfill cc_addr/bcc_addr abgeschlossen.
- Produktion: 192.168.1.131 — via `update.sh` (commit 62693fa), Backend+Frontend laufen, Manticore-Reindex 129/129 (0 Fehler).

## Implementation Notes (Backend)
- `internal/userstore/userstore.go`: Migration `ALTER TABLE users ADD COLUMN IF NOT EXISTS list_page_size INT NOT NULL DEFAULT 25` (in `initSchema`); `User.ListPageSize int` Feld; alle SELECT/RETURNING-Queries und `scanUser`/`scanUserRow`/`VerifyPassword`/`UpsertLDAPUser` um `list_page_size` erweitert; neue Methode `UpdateListPageSize(ctx, userID, pageSize)`.
- `internal/api/profile_handlers.go`: neuer Handler `handleChangePreferences` (PATCH /api/auth/preferences), validiert gegen `allowedListPageSizes` (25/50/100/200), 400 bei ungültigem Wert, 200 mit `{"ok":true,"list_page_size":N}`. Kein Audit-Log (bewusst, kleine Präferenz).
- `internal/api/server.go`: Route `PATCH /api/auth/preferences` registriert (auth-geschützt).
- `internal/api/auth_handlers.go`: `handleLogin`- und `handleMe`-Responses liefern zusätzlich `list_page_size`.

## Implementation Notes (Frontend)
- `src/lib/api/users.ts`: `MeResponse` um `list_page_size: number` erweitert; neue Funktion `updatePreferences(listPageSize)` → `PATCH /api/auth/preferences`.
- `src/lib/api/index.ts`: `updatePreferences` re-exportiert.
- `src/hooks/useAuth.ts`: neue `refresh()`-Funktion (refetch `/api/auth/me`, aktualisiert Cache + State) zurückgegeben.
- `src/app/settings/page.tsx`: neue Card "Listenansicht" mit Select "Einträge pro Seite" (25/50/100/200), initialisiert aus `user.list_page_size`, ruft bei Änderung `updatePreferences()` auf, zeigt Erfolg/Fehler via `Alert`, danach `refresh()` für sofortige Wirkung.
- `src/app/search/page.tsx`: Konstante `PAGE_SIZE` entfernt, ersetzt durch `pageSize = user?.list_page_size ?? DEFAULT_PAGE_SIZE` (DEFAULT_PAGE_SIZE = 25). Verwendet in `doSearch`, initialem Such-`useEffect`, `handleApplySavedSearch` und `totalPages`-Berechnung.
