# PROJ-9: Ordner- & Label-Verwaltung

## Status: Removed
**Created:** 2026-03-12
**Last Updated:** 2026-04-04

## 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_
