feat(PROJ-9): implement labels backend - DB schema, labelstore, API handlers, Xapian integration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+2
-2
@@ -20,7 +20,7 @@
|
||||
| PROJ-6 | Volltext-Suche & Filterung | Deployed | [PROJ-6](PROJ-6-volltext-suche.md) | 2026-03-12 |
|
||||
| PROJ-7 | E-Mail-Ansicht (Lesen & Anhänge) | Deployed | [PROJ-7](PROJ-7-email-ansicht.md) | 2026-03-12 |
|
||||
| PROJ-8 | Automatischer IMAP-Sync (Cron-Job) | Deployed | [PROJ-8](PROJ-8-imap-auto-sync.md) | 2026-03-12 |
|
||||
| PROJ-9 | Ordner- & Label-Verwaltung | In Progress | [PROJ-9](PROJ-9-ordner-und-labels.md) | 2026-03-12 |
|
||||
| PROJ-9 | Ordner- & Label-Verwaltung | In Review | [PROJ-9](PROJ-9-ordner-und-labels.md) | 2026-03-12 |
|
||||
| PROJ-10 | Admin-Bereich: Nutzer- & Postfachverwaltung | Deployed | [PROJ-10](PROJ-10-admin-bereich.md) | 2026-03-12 |
|
||||
| PROJ-11 | Audit-Log & Compliance-Berichte | Deployed | [PROJ-11](PROJ-11-audit-log.md) | 2026-03-12 |
|
||||
| PROJ-12 | E-Mail-Export (EML/PDF) | Deployed | [PROJ-12](PROJ-12-export.md) | 2026-03-12 |
|
||||
@@ -35,7 +35,7 @@
|
||||
| PROJ-20 | Nutzer-Löschung & E-Mail-Verbleib (GoBD-konform) | Deployed | [PROJ-20](PROJ-20-nutzer-loeschung.md) | 2026-03-17 |
|
||||
| PROJ-21 | Multi-Mandanten-Fähigkeit (Multi-Tenancy) | Deployed | [PROJ-21](PROJ-21-multi-tenancy.md) | 2026-03-17 |
|
||||
| PROJ-22 | LDAP / AD – Web-GUI Konfiguration & Test | Deployed | [PROJ-22](PROJ-22-ldap-webgui.md) | 2026-03-17 |
|
||||
| PROJ-23 | Pro-Mandant LDAP / Active Directory (Multi-Tenant Phase B) | In Progress | [PROJ-23](PROJ-23-tenant-ldap-pro-mandant.md) | 2026-03-17 |
|
||||
| PROJ-23 | Pro-Mandant LDAP / Active Directory (Multi-Tenant Phase B) | Deployed | [PROJ-23](PROJ-23-tenant-ldap-pro-mandant.md) | 2026-03-17 |
|
||||
| PROJ-24 | TOTP Zwei-Faktor-Authentifizierung (2FA) | Deployed | [PROJ-24](PROJ-24-totp-zwei-faktor.md) | 2026-03-18 |
|
||||
| PROJ-25 | User-Profil & Einstellungen | Deployed | [PROJ-25](PROJ-25-user-profil-einstellungen.md) | 2026-03-18 |
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
# 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_
|
||||
Reference in New Issue
Block a user