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>
This commit is contained in:
sysops
2026-03-14 19:49:00 +01:00
parent 30479cfc60
commit 850290b5ef
7 changed files with 703 additions and 4 deletions
+29
View File
@@ -391,3 +391,32 @@ export interface SystemStats {
export async function getSystemStats(): Promise<SystemStats> {
return request<SystemStats>("/api/admin/system/stats");
}
// ── Export ────────────────────────────────────────────────────────────────
export async function exportMailPDF(id: string): Promise<{ blob: Blob; filename: string }> {
const token = getToken();
const res = await fetch(`${API_BASE}/api/export/pdf/${id}`, {
headers: token ? { Authorization: `Bearer ${token}` } : {},
});
if (!res.ok) throw new Error("PDF export failed");
const blob = await res.blob();
const cd = res.headers.get("Content-Disposition") || "";
const filename = cd.match(/filename="([^"]+)"/)?.[1] || `${id.slice(0, 16)}.pdf`;
return { blob, filename };
}
export async function exportMailsZIP(ids: string[], attachments: boolean): Promise<{ blob: Blob }> {
const token = getToken();
const res = await fetch(`${API_BASE}/api/export/zip`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
},
body: JSON.stringify({ ids, attachments }),
});
if (!res.ok) throw new Error("ZIP export failed");
const blob = await res.blob();
return { blob };
}