Files
patrickandClaude Sonnet 5 a6708f802c
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) 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 / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (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
UX-10: dms-upload-E2E und Upload-Meldungen
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 02:04:35 +02:00

93 lines
2.7 KiB
TypeScript

// Duenner Client fuer DOC-03s Backend-Voraussetzung (dms/internal/uploadapi) —
// DOC-01 lieferte nur einen Go-Service ohne HTTP-Endpunkt, siehe DOC-03-PRUEFPROTOKOLL.md.
import { describeHttpError } from "@nexarch/shl";
import { apiFetch } from "./http";
export const CHUNK_SIZE_BYTES = 4 * 1024 * 1024;
function apiBase(): string {
const base = process.env.NEXT_PUBLIC_UPLOAD_API_URL;
if (!base) {
throw new Error("NEXT_PUBLIC_UPLOAD_API_URL ist nicht gesetzt (Umgebungsvariable erforderlich)");
}
return base;
}
export interface StartUploadResult {
sessionId: string;
}
export async function startUpload(filename: string, mimeType: string, totalSize: number): Promise<StartUploadResult> {
const res = await apiFetch(`${apiBase()}/uploads`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ filename, mimeType, totalSize }),
});
if (!res.ok) {
throw new Error(await extractErrorMessage(res));
}
return res.json();
}
export interface ChunkUploadResult {
bytesReceived: number;
}
export async function uploadChunk(sessionId: string, offset: number, chunk: Blob): Promise<ChunkUploadResult> {
const res = await apiFetch(`${apiBase()}/uploads/${encodeURIComponent(sessionId)}/chunk?offset=${offset}`, {
method: "PUT",
body: chunk,
});
if (!res.ok) {
throw new Error(await extractErrorMessage(res));
}
return res.json();
}
export interface UploadStatus {
sessionId: string;
filename: string;
totalSize: number;
bytesReceived: number;
status: string;
documentId?: string;
}
export async function getUploadStatus(sessionId: string): Promise<UploadStatus> {
const res = await apiFetch(`${apiBase()}/uploads/${encodeURIComponent(sessionId)}`);
if (!res.ok) {
throw new Error(await extractErrorMessage(res));
}
return res.json();
}
export interface CompleteUploadResult {
documentId: string;
revisionId: string;
}
export async function completeUpload(sessionId: string): Promise<CompleteUploadResult> {
const res = await apiFetch(`${apiBase()}/uploads/${encodeURIComponent(sessionId)}/complete`, { method: "POST" });
if (!res.ok) {
throw new Error(await extractErrorMessage(res));
}
return res.json();
}
async function extractErrorMessage(res: Response): Promise<string> {
if (res.status === 403 || res.status === 503) return describeHttpError(res.status);
try {
const text = await res.text();
try {
const message = JSON.parse(text)?.error?.message;
if (typeof message === "string" && message) return message;
} catch {
// kein JSON: Klartext unverändert verwenden
}
return text || `Upload-API antwortete mit Status ${res.status}`;
} catch {
return `Upload-API antwortete mit Status ${res.status}`;
}
}