CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Mail-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Detailansicht fuer web/mail-postfach (UX-01-App erweitert, nicht neu gebaut): Header (Von/Betreff/Ordner/Datum/Nachricht-ID), Nachrichtentext, Anhangsliste mit Download (Dateiname nicht verfuegbar -- System persistiert keinen Originalnamen, daher Anhang N + aus dem Content-Type geratene Endung). Thread-Gruppierung ueber normalisierten Betreff (keine In-Reply-To/References-Header im System), Navigation zwischen Thread-Nachrichten als reiner Komponentenzustand derselben Seite (kein Routenwechsel -- Ordner-/Suchkontext bleibt dadurch strukturell erhalten, Akzeptanzkriterium 3). UX-03 (eigener offener Branch, eigenes Frontend web/mail-search) wird NICHT wiederverwendet -- getrennte Apps ohne Komponentenfreigabe, siehe UX-02-PRUEFPROTOKOLL.md fuer die Abgrenzung und die Begruendung der Backend-Erweiterung. Tests: lib/threading, lib/threadNavigation, lib/attachmentNaming, components/MessageDetail (Komponententest je Akzeptanzkriterium), __tests__/a11y (jest-axe, Pruefung 3), e2e/ux02 gegen die echte mailpostfach-fixture (Pruefungen 1+2, Mandantentrennung). Bestehender e2e/ux01-Satz bleibt unveraendert gruen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
99 lines
3.4 KiB
TypeScript
99 lines
3.4 KiB
TypeScript
// UX-01: dünner Client für mail/internal/mailapi (INT-01, bereits
|
|
// fertig) — keine eigene Sortier-/Filterlogik, die läuft in
|
|
// lib/sorting.ts rein clientseitig auf der bereits mandantengebundenen
|
|
// Antwort weiter.
|
|
import { apiFetch, statusMessage } from "./http";
|
|
|
|
export interface MailboxDTO {
|
|
name: string;
|
|
messageCount: number;
|
|
}
|
|
|
|
export interface MessageListItemDTO {
|
|
messageId: string;
|
|
subject: string;
|
|
sender: string;
|
|
mailbox: string;
|
|
sentAt: number;
|
|
}
|
|
|
|
// Fetcher erlaubt es Tests, einen echten Bearer-Token direkt gegen eine
|
|
// echte mailapi-Instanz zu senden, ohne den vollen Core-Anmeldefluss
|
|
// (lib/http.ts) nachzubauen — der Tenant-Isolationscode läuft dabei
|
|
// unverändert auf dem echten Server, nur die Token-Beschaffung ist
|
|
// ausgetauscht.
|
|
export type Fetcher = (input: string, init?: RequestInit) => Promise<Response>;
|
|
|
|
function apiBase(): string {
|
|
const base = process.env.NEXT_PUBLIC_MAIL_API_URL;
|
|
if (!base) {
|
|
throw new Error("NEXT_PUBLIC_MAIL_API_URL ist nicht gesetzt (Umgebungsvariable erforderlich)");
|
|
}
|
|
return base.replace(/\/$/, "");
|
|
}
|
|
|
|
async function get<T>(path: string, fetcher: Fetcher): Promise<T> {
|
|
const res = await fetcher(`${apiBase()}${path}`);
|
|
if (!res.ok) {
|
|
throw new Error(statusMessage(res, "Anfrage fehlgeschlagen"));
|
|
}
|
|
return res.json();
|
|
}
|
|
|
|
export async function fetchMailboxes(fetcher: Fetcher = apiFetch): Promise<MailboxDTO[]> {
|
|
const body = await get<{ mailboxes: MailboxDTO[] }>("/mailboxes", fetcher);
|
|
return body.mailboxes;
|
|
}
|
|
|
|
export interface MessagesQuery {
|
|
mailbox?: string;
|
|
q?: string;
|
|
}
|
|
|
|
export async function fetchMessages(query: MessagesQuery = {}, fetcher: Fetcher = apiFetch): Promise<MessageListItemDTO[]> {
|
|
const params = new URLSearchParams();
|
|
if (query.mailbox) params.set("mailbox", query.mailbox);
|
|
if (query.q) params.set("q", query.q);
|
|
const qs = params.toString() ? `?${params.toString()}` : "";
|
|
const body = await get<{ messages: MessageListItemDTO[] }>(`/messages${qs}`, fetcher);
|
|
return body.messages;
|
|
}
|
|
|
|
// UX-02: Detailansicht. Felder entsprechen bereits den "relevanten
|
|
// Headern" (Von/Betreff/Ordner/Datum/Nachricht-ID) — mailapi hat keine
|
|
// To/Cc-Felder, weil der Ingestion-Pfad sie nirgends persistiert.
|
|
export interface MessageDetailDTO {
|
|
messageId: string;
|
|
subject: string;
|
|
body: string;
|
|
sender: string;
|
|
mailbox: string;
|
|
sentAt: number;
|
|
}
|
|
|
|
export async function fetchMessageDetail(messageId: string, fetcher: Fetcher = apiFetch): Promise<MessageDetailDTO> {
|
|
return get<MessageDetailDTO>(`/messages/${encodeURIComponent(messageId)}`, fetcher);
|
|
}
|
|
|
|
// AttachmentDTO hat bewusst keinen Dateinamen -- die API liefert keinen
|
|
// (siehe mailapi.attachmentListItem), nur Index/ContentType/Size aus
|
|
// den tatsaechlich gespeicherten Bytes.
|
|
export interface AttachmentDTO {
|
|
index: number;
|
|
contentType: string;
|
|
size: number;
|
|
}
|
|
|
|
export async function fetchAttachments(messageId: string, fetcher: Fetcher = apiFetch): Promise<AttachmentDTO[]> {
|
|
const body = await get<{ attachments: AttachmentDTO[] }>(`/messages/${encodeURIComponent(messageId)}/attachments`, fetcher);
|
|
return body.attachments;
|
|
}
|
|
|
|
export async function fetchAttachmentContent(messageId: string, index: number, fetcher: Fetcher = apiFetch): Promise<Blob> {
|
|
const res = await fetcher(`${apiBase()}/messages/${encodeURIComponent(messageId)}/attachments/${index}`);
|
|
if (!res.ok) {
|
|
throw new Error(statusMessage(res, "Anhang-Abruf fehlgeschlagen"));
|
|
}
|
|
return res.blob();
|
|
}
|