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
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
93 lines
2.7 KiB
TypeScript
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}`;
|
|
}
|
|
}
|