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

182 lines
8.5 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.
## Implementation Notes (Frontend)
**Implemented 2026-03-18:**
- `src/lib/api.ts` -- Added `MailLabel` and `LabelRule` interfaces plus all label API functions (getLabels, createLabel, updateLabel, deleteLabel, assignLabel, removeLabelFromEmail, getMailLabelIds, createAdminLabel, getAdminLabels, deleteAdminLabel, getLabelRules, createLabelRule, deleteLabelRule). Added `label_id` parameter to `searchEmails()`. Named interface `MailLabel` to avoid conflict with shadcn `Label` component.
- `src/components/LabelList.tsx` -- Sidebar component showing all labels with colored circles, selection state, create/edit/delete inline forms, color picker (8 preset colors). Global labels show lock icon and cannot be deleted. Hidden on mobile (`hidden md:block`).
- `src/components/LabelPicker.tsx` -- Popover-based label assignment widget for mail detail view. Shows assigned labels as colored badges. Popover lists all labels with checkbox-style toggles for assign/remove.
- `src/app/search/page.tsx` -- Integrated LabelList as left sidebar (w-48). Added `selectedLabelId` state. Label selection triggers re-search with `label_id` parameter. Layout uses flex with sidebar and main content area.
- `src/app/mail/[id]/page.tsx` -- Integrated LabelPicker after mail header card. Loads all labels and assigned label IDs on mount. Updates label assignments via `onUpdate` callback.
- `src/app/admin/page.tsx` -- Added "Labels" tab (superadmin only) with two sections: Global Labels management (create/delete with color picker) and Auto-Rules management (create/delete with condition field/value/label selection).
## QA Test Results
_To be added by /qa_
## Deployment
_To be added by /deploy_