Files
archivmail/features/PROJ-12-export.md
T
sysops 850290b5ef feat(PROJ-12): E-Mail Export EML/PDF/ZIP
- 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>
2026-03-14 19:49:00 +01:00

150 lines
6.7 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-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_