850290b5ef
- GET /api/export/pdf/{id}: PDF-Generierung (stdlib, kein ext. Paket)
- POST /api/export/zip: Streaming-ZIP mit manifest.csv, Anhänge optional
- Max. 500 Mails pro Export, Zugriffscheck per Rolle
- Audit-Log für jeden Export
- Frontend: PDF-Button in Mail-Ansicht
- Frontend: Checkboxen + ZIP-Export-Dialog in Suchergebnissen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
150 lines
6.7 KiB
Markdown
150 lines
6.7 KiB
Markdown
# PROJ-12: E-Mail-Export (EML / PDF)
|
||
|
||
## Status: In Review
|
||
**Created:** 2026-03-12
|
||
**Last Updated:** 2026-03-14
|
||
|
||
## Dependencies
|
||
- Requires: PROJ-1 (Authentifizierung)
|
||
- Requires: PROJ-6 (Volltext-Suche) – Export aus Suchergebnissen
|
||
- Requires: PROJ-7 (E-Mail-Ansicht)
|
||
- Requires: PROJ-11 (Audit-Log) – Export-Aktionen werden geloggt
|
||
|
||
## User Stories
|
||
- Als Nutzer möchte ich eine einzelne E-Mail als EML-Datei exportieren, damit ich sie in einem E-Mail-Client öffnen kann.
|
||
- Als Nutzer möchte ich eine E-Mail als PDF drucken/exportieren, damit ich sie für Behörden oder Verträge verwenden kann.
|
||
- Als Admin möchte ich mehrere E-Mails als ZIP-Archiv exportieren, damit ich bei einer Anfrage mehrere Mails auf einmal liefern kann.
|
||
- Als System möchte ich jeden Export im Audit-Log erfassen.
|
||
|
||
## Acceptance Criteria
|
||
- [ ] Einzelexport EML: Original-MIME-Inhalt wird unverändert heruntergeladen
|
||
- [ ] Einzelexport PDF: E-Mail-Header + Body als gut lesbares PDF gerendert, Anhänge als separate Dateien erwähnt
|
||
- [ ] Massenexport: Auswahl mehrerer E-Mails (Checkbox in Suchergebnissen), ZIP-Download
|
||
- [ ] ZIP enthält: EML-Dateien + optionale Anhänge + Manifest (CSV mit Metadaten)
|
||
- [ ] Massenexport-Limit konfigurierbar (Standard: max. 500 E-Mails pro Export)
|
||
- [ ] Jeder Export wird im Audit-Log erfasst (Nutzer, Anzahl E-Mails, Format)
|
||
- [ ] Zugriffsschutz: Nutzer kann nur eigene E-Mails exportieren
|
||
|
||
## Edge Cases
|
||
- Export von 500 E-Mails mit großen Anhängen → Streaming-ZIP, kein Speicher-Overflow
|
||
- PDF-Rendering von komplexem HTML → graceful Fallback auf Plain-Text-PDF
|
||
- Nutzer wählt E-Mails aus, auf die er keinen Zugriff hat → diese werden aus Export-Liste entfernt
|
||
|
||
## Technical Requirements
|
||
- ZIP-Erstellung als Stream (nicht komplett in Memory)
|
||
- PDF-Generierung serverseitig (z.B. wkhtmltopdf oder Go-PDF-Bibliothek)
|
||
|
||
---
|
||
## Tech Design (Solution Architect)
|
||
|
||
### Komponentenstruktur
|
||
|
||
**Next.js Frontend:**
|
||
```
|
||
Suchergebnisse (PROJ-6, Erweiterung)
|
||
├── Checkbox pro Treffer (Multi-Select)
|
||
├── [Exportieren] Button (aktiv wenn ≥1 ausgewählt)
|
||
└── Export-Dialog
|
||
├── Format: EML | PDF
|
||
├── Anhänge einschließen: ja / nein
|
||
└── [Download starten] → POST /api/export/zip (Streaming)
|
||
|
||
E-Mail-Ansicht (PROJ-7, Erweiterung)
|
||
├── [Als EML herunterladen] Button → GET /api/export/eml/{id}
|
||
└── [Als PDF exportieren] Button → GET /api/export/pdf/{id}
|
||
```
|
||
|
||
**Go Backend:**
|
||
```
|
||
Export-Handler
|
||
├── GET /api/export/eml/{id} ← Einzelexport EML
|
||
│ └── Original-.m-Datei lesen, AES-256-GCM entschlüsseln, direkt streamen
|
||
│
|
||
├── GET /api/export/pdf/{id} ← Einzelexport PDF
|
||
│ ├── .m-Datei lesen + entschlüsseln
|
||
│ ├── Header + Body extrahieren
|
||
│ └── → PDF-Bibliothek → PDF streamen
|
||
│ Fallback: Plain-Text-PDF wenn HTML zu komplex
|
||
│
|
||
└── POST /api/export/zip ← Massenexport
|
||
├── Body: { ids: [...], format: "eml"|"pdf", attachments: bool }
|
||
├── Zugriffscheck: Nutzer darf nur eigene Mails exportieren
|
||
├── Max-Limit prüfen (konfigurierbar, Standard: 500)
|
||
├── Streaming-ZIP (archive/zip Writer → ResponseWriter direkt)
|
||
│ ├── Pro Mail: <message_id>.eml oder <message_id>.pdf
|
||
│ ├── Anhänge: attachments/<hash>/<filename> (wenn aktiviert)
|
||
│ └── manifest.csv (Message-ID, From, To, Subject, Date, Dateiname)
|
||
└── → Audit-Log: export_start + export_done (Anzahl, Format)
|
||
```
|
||
|
||
### Export-Fluss (Massenexport)
|
||
|
||
```
|
||
POST /api/export/zip
|
||
│
|
||
├── Zugriffsfilter: IDs auf user-eigene Mails beschränken
|
||
│
|
||
├── Audit-Log: export_start
|
||
│
|
||
├── ZIP-Stream öffnen (Content-Type: application/zip)
|
||
│
|
||
└── Für jede Mail:
|
||
.m-Datei lesen → AES-256-GCM entschlüsseln
|
||
→ Zu ZIP hinzufügen (kein vollständiges RAM-Buffering)
|
||
↓
|
||
manifest.csv Zeile anhängen
|
||
↓
|
||
ZIP schließen → Audit-Log: export_done
|
||
```
|
||
|
||
### Technische Entscheidungen
|
||
|
||
| Entscheidung | Begründung |
|
||
|---|---|
|
||
| **Streaming-ZIP** | 500 Mails mit Anhängen können mehrere GB sein — kein RAM-Overhead |
|
||
| **Serverseitiges PDF** | Browser-Print ist nicht reproduzierbar; serverseitiges PDF ist auditierbar und einheitlich |
|
||
| **Plain-Text-Fallback für PDF** | Komplexes HTML kann PDF-Renderer zum Absturz bringen — graceful degradation |
|
||
| **Zugriffscheck im Export-Handler** | Serverseitige Filterung verhindert Datenlecks durch manipulierte IDs |
|
||
| **manifest.csv im ZIP** | Nachvollziehbarkeit bei Behördenanfragen ohne jede EML einzeln öffnen zu müssen |
|
||
| **Audit-Log für jeden Export** | Compliance-Anforderung — wer hat wann was exportiert |
|
||
|
||
### Abhängigkeiten
|
||
|
||
| Paket | Zweck |
|
||
|---|---|
|
||
| `archive/zip` (Stdlib) | Streaming-ZIP-Erstellung ohne externe Abhängigkeit |
|
||
| `github.com/SebastiaanKlippert/go-wkhtmltopdf` | PDF-Generierung aus HTML (serverseitig) |
|
||
|
||
## Implementation Notes (2026-03-14)
|
||
|
||
### What was built
|
||
|
||
**Go Backend — `internal/api/export.go` (new file):**
|
||
- `GET /api/export/pdf/{id}` — generates a stdlib-only PDF (no external deps) using raw PDF 1.4 syntax with Helvetica core font. Renders header fields, plain-text body (with HTML-strip fallback), and attachment list. `toLatinSafe()` converts umlauts for Latin-1 compatibility.
|
||
- `POST /api/export/zip` — streaming ZIP via `archive/zip` stdlib. Accepts `{"ids":[...], "attachments": true}`, max 500 IDs. Adds `{id[:16]}.eml`, optional `attachments/{id[:8]}/{filename}` entries, and `manifest.csv` (CSV with filename/message_id/from/to/subject/date). Audit-logged as `export: zip: N mails`.
|
||
- Both handlers use `requireMailAccess` middleware (blocks admin role). RoleUser is filtered to own mails via `mailBelongsToUser`; RoleAuditor can export all.
|
||
|
||
**Deviation from spec:** PDF generation uses a hand-rolled stdlib PDF writer instead of `go-wkhtmltopdf` or `go-pdf/fpdf` — avoids adding an external dependency that would require `go get` on the server.
|
||
|
||
**Routes added to `internal/api/server.go`:**
|
||
- `GET /api/export/pdf/{id}`
|
||
- `POST /api/export/zip`
|
||
|
||
**Frontend `src/lib/api.ts`:**
|
||
- Added `exportMailPDF(id)` and `exportMailsZIP(ids, attachments)` export functions.
|
||
|
||
**Frontend `src/app/mail/[id]/page.tsx`:**
|
||
- Added "Als PDF exportieren" button next to "Als .eml herunterladen".
|
||
|
||
**Frontend `src/app/search/page.tsx`:**
|
||
- Added per-row Checkbox column + select-all header checkbox.
|
||
- Export toolbar appears when ≥1 mail is selected.
|
||
- ZIP export dialog with attachments toggle (Switch component).
|
||
- Selection cleared when search results change.
|
||
|
||
## QA Test Results
|
||
_To be added by /qa_
|
||
|
||
## Deployment
|
||
_To be added by /deploy_
|