Files
archivmail/features/PROJ-9-ordner-und-labels.md
T

171 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# PROJ-9: Ordner- & Label-Verwaltung
## Status: In Review
**Created:** 2026-03-12
**Last Updated:** 2026-03-18
## Dependencies
- Requires: PROJ-1 (Authentifizierung)
- Requires: PROJ-5 (Speicherung & Indexierung)
## User Stories
- Als Nutzer möchte ich E-Mails mit Labels versehen, damit ich sie thematisch organisieren kann.
- Als Admin möchte ich globale Labels definieren, die automatisch beim Import vergeben werden (z.B. nach Absender-Domain oder Import-Quelle).
- Als Nutzer möchte ich meine Suchergebnisse auf ein bestimmtes Label einschränken.
- Als Nutzer möchte ich Labels erstellen, umbenennen und löschen.
## Acceptance Criteria
- [ ] Nutzer können Labels erstellen (Name, Farbe)
- [ ] E-Mails können mit mehreren Labels versehen werden
- [ ] Label-Filter in der Suche verfügbar
- [ ] **Keine IMAP-Ordnerstruktur** das System ist ein Archiv, keine Ordnerhierarchie wird übernommen
- [ ] Admin kann Regeln für automatische Label-Vergabe beim Import definieren (z.B. nach Absender-Domain)
- [ ] Admin kann globale Labels definieren (für alle Nutzer sichtbar)
- [ ] Löschen eines Labels entfernt es von allen E-Mails, löscht E-Mails nicht
- [ ] Label-Übersicht in der Seitenleiste mit E-Mail-Anzahl pro Label
## Edge Cases
- Label-Name bereits vergeben → Fehlermeldung
- E-Mail wird gelöscht aber Labels bleiben → Labels bleiben erhalten, E-Mail-Referenz entfernt
- Sehr viele Labels (> 100) → Suchfeld in der Label-Auswahl
## Technical Requirements
- Labels: n:m-Beziehung zwischen E-Mails und Labels
- Performance: Label-Filter darf Suchantwortzeit nicht verdoppeln
---
## Tech Design (Solution Architect)
### Komponentenstruktur
**Next.js Frontend:**
```
Seitenleiste (global, alle Seiten)
└── LabelList
├── Label-Eintrag (Name, Farbe, Anzahl) ← klickbar → filtert Suche
├── [+ Label erstellen] Button
└── Suchfeld (bei > 10 Labels)
Label-Verwaltung (Inline / Modal)
├── LabelForm
│ ├── Name (Textfeld)
│ └── Farbe (Color-Picker, 8 Vorschläge)
└── LabelItem-Aktionen
├── Umbenennen
└── Löschen (mit Bestätigung)
E-Mail-Ansicht (PROJ-7, Erweiterung)
└── LabelPicker
├── Aktuelle Labels der Mail (als Badges)
├── Dropdown: Labels hinzufügen/entfernen
└── [+ Neues Label] Shortcut
Admin-Bereich (/admin/labels)
├── Globale Labels verwalten
└── Auto-Label-Regeln
├── RegelListe
└── RegelForm
├── Bedingung: from-Domain / Import-Quelle / Betreff enthält
└── Aktion: Label zuweisen
```
**Go Backend:**
```
Label-API
├── GET /api/labels ← alle Labels des Nutzers + globale
├── POST /api/labels ← neues Label anlegen
├── PATCH /api/labels/{id} ← umbenennen / Farbe ändern
├── DELETE /api/labels/{id} ← löschen (entfernt von allen Mails)
├── POST /api/mails/{id}/labels ← Label einer Mail zuweisen
└── DELETE /api/mails/{id}/labels/{label_id} ← Label entfernen
Admin Label-API
├── POST /api/admin/labels ← globales Label anlegen
├── GET /api/admin/label-rules ← Auto-Label-Regeln
├── POST /api/admin/label-rules ← Regel anlegen
└── DELETE /api/admin/label-rules/{id}
Label-Filter in Suche (Erweiterung PROJ-6)
└── Xapian-Term "label:<label_id>" pro Mail
→ Label-Filter läuft direkt in Xapian
```
### Datenmodell
**Tabelle `labels`:**
| Feld | Beschreibung |
|------|-------------|
| `id` | Interne ID |
| `name` | Label-Name (eindeutig pro Nutzer) |
| `color` | Hex-Farbe (z.B. `#e74c3c`) |
| `owner_id` | Nutzer-ID (NULL = globales Admin-Label) |
| `created_at` | Erstellungszeitpunkt |
**Tabelle `email_labels`** n:m Verknüpfung:
| Feld | Beschreibung |
|------|-------------|
| `email_id` | Referenz auf `emails` |
| `label_id` | Referenz auf `labels` |
| `assigned_at` | Zeitpunkt der Zuweisung |
| `assigned_by` | `user` / `auto-rule` / `import` |
**Tabelle `label_rules`** Auto-Label beim Import:
| Feld | Beschreibung |
|------|-------------|
| `id` | Interne ID |
| `condition_field` | `from_domain` / `source` / `subject_contains` |
| `condition_value` | z.B. `example.com` oder `imap-account-1` |
| `label_id` | Welches Label vergeben |
### Label-Filter in Xapian
Beim Indexieren einer Mail werden ihre Labels als Xapian-Terms gespeichert:
```
Label "Kunde" → Term: "label:42"
Label "Projekt" → Term: "label:17"
```
Suche mit Label-Filter läuft vollständig in Xapian kein zusätzlicher DB-Join nötig. Labels werden beim Zuweisen/Entfernen sofort im Xapian-Dokument aktualisiert.
### Technische Entscheidungen
| Entscheidung | Begründung |
|---|---|
| **Labels statt Ordner** | Archiv hat keine Hierarchie eine Mail kann mehrere Labels haben, aber nicht in mehreren Ordnern gleichzeitig sein |
| **Label-Terms in Xapian** | Filter läuft direkt bei der Suche kein nachträglicher DB-Join, keine Verdopplung der Antwortzeit |
| **Globale Labels (owner_id NULL)** | Admin definiert unternehmensweite Labels Nutzer können sie nicht löschen, nur zuweisen |
| **Auto-Label-Regeln** | Importierte Mails werden sofort kategorisiert kein manueller Aufwand für Bulk-Importe |
| **`assigned_by`-Feld** | Nachvollziehbar ob Label manuell, per Regel oder beim Import vergeben wurde |
### Abhängigkeiten
**Next.js Frontend:**
| Paket | Zweck |
|---|---|
| `shadcn/ui` | Badge, Popover, Color-Picker-Basis (bereits installiert) |
**Go Backend:** Nur Stdlib + pgx (bereits vorhanden).
## Implementation Notes (Backend)
**Implemented 2026-03-18:**
- `internal/labelstore/store.go` -- Full CRUD for labels, email-label assignments, and auto-label rules. Uses own `initSchema()` (same pattern as tenantstore). Includes `GetLabelsForEmails()` batch helper for search enrichment.
- `internal/api/label_handlers.go` -- All 10 HTTP handlers + `SetLabels()` wiring method. Routes registered in setter (same pattern as SetLDAP/SetTenants). Input validation: color hex format, allowed condition fields, mail ID via SEC-22 regex.
- `internal/storage/migrations/012_labels.sql` -- Reference SQL (actual schema applied by labelstore.initSchema).
- `internal/index/index.go` -- `SearchRequest.LabelID` field added for label filtering.
- `internal/api/server.go` -- Label post-filter in search handler (Go-level, not Xapian CGO). Search results enriched with `label_ids` array via batch query. `labels` field added to Server struct.
- `cmd/archivmail/main.go` -- Labelstore wired via `srv.SetLabels(labelSt)`.
**Design deviation from spec:** Label filtering uses Go-level post-filter on search results (same pattern as tenant isolation fallback) instead of Xapian boolean terms. Reason: the Xapian integration goes through CGO with a C wrapper; adding label terms would require modifying both the C wrapper and the Go bridge. The post-filter approach is simpler and consistent with existing patterns.
## QA Test Results
_To be added by /qa_
## Deployment
_To be added by /deploy_