CSV-Export in den Listen-Views (Voelker, Honigchargen) und PDF-Rueckverfolgbarkeitsbericht auf der Honigcharge-Detailseite. apiFetchBlob() im API-Client ergaenzt fuer authentifizierte Blob-Downloads (JWT im Authorization-Header statt Cookie).
87 lines
2.3 KiB
TypeScript
87 lines
2.3 KiB
TypeScript
import { getAccessToken, setAccessToken } from './token-store';
|
|
import { refreshRequest } from './api';
|
|
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3001/api/v1';
|
|
|
|
export class ApiError extends Error {
|
|
status: number;
|
|
|
|
constructor(status: number, message: string) {
|
|
super(message);
|
|
this.status = status;
|
|
}
|
|
}
|
|
|
|
async function rawFetch(path: string, init: RequestInit): Promise<Response> {
|
|
const token = getAccessToken();
|
|
return fetch(`${API_URL}${path}`, {
|
|
...init,
|
|
credentials: 'include',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
...init.headers,
|
|
},
|
|
});
|
|
}
|
|
|
|
export interface BlobDownload {
|
|
blob: Blob;
|
|
filename: string;
|
|
}
|
|
|
|
function filenameFromContentDisposition(header: string | null, fallback: string): string {
|
|
const match = header ? /filename="([^"]+)"/.exec(header) : null;
|
|
return match?.[1] ?? fallback;
|
|
}
|
|
|
|
export async function apiFetchBlob(path: string, fallbackFilename: string): Promise<BlobDownload> {
|
|
let response = await rawFetch(path, {});
|
|
|
|
if (response.status === 401) {
|
|
const refreshed = await refreshRequest();
|
|
if (refreshed) {
|
|
setAccessToken(refreshed.accessToken);
|
|
response = await rawFetch(path, {});
|
|
}
|
|
}
|
|
|
|
if (!response.ok) {
|
|
throw new ApiError(response.status, 'Export fehlgeschlagen');
|
|
}
|
|
|
|
const blob = await response.blob();
|
|
const filename = filenameFromContentDisposition(
|
|
response.headers.get('Content-Disposition'),
|
|
fallbackFilename,
|
|
);
|
|
return { blob, filename };
|
|
}
|
|
|
|
export async function apiFetch<T>(path: string, init: RequestInit = {}): Promise<T> {
|
|
let response = await rawFetch(path, init);
|
|
|
|
if (response.status === 401) {
|
|
const refreshed = await refreshRequest();
|
|
if (refreshed) {
|
|
setAccessToken(refreshed.accessToken);
|
|
response = await rawFetch(path, init);
|
|
}
|
|
}
|
|
|
|
if (!response.ok) {
|
|
const body: unknown = await response.json().catch(() => ({}));
|
|
const message =
|
|
typeof body === 'object' && body !== null && 'message' in body
|
|
? String((body as { message: unknown }).message)
|
|
: 'Request failed';
|
|
throw new ApiError(response.status, message);
|
|
}
|
|
|
|
if (response.status === 204) {
|
|
return undefined as T;
|
|
}
|
|
|
|
return response.json() as Promise<T>;
|
|
}
|