refactor(api): route baserow calls through internal proxy

This commit is contained in:
m3ta-chiron
2026-04-30 08:03:30 +02:00
parent 8770e60c93
commit dee96ee1cc

View File

@@ -1,13 +1,3 @@
const BASE = import.meta.env.VITE_BASEROW_URL;
const TOKEN = import.meta.env.VITE_BASEROW_TOKEN;
const SERVERS_TABLE = import.meta.env.VITE_SERVERS_TABLE_ID;
const PROCESSES_TABLE = import.meta.env.VITE_PROCESSES_TABLE_ID;
const headers = {
Authorization: `Token ${TOKEN}`,
'Content-Type': 'application/json',
};
/* ── Types ─────────────────────────────────────────── */ /* ── Types ─────────────────────────────────────────── */
export interface Server { export interface Server {
@@ -27,78 +17,106 @@ export interface Process {
Farbe?: string; Farbe?: string;
Start?: string; Start?: string;
Ende?: string; Ende?: string;
'Start Minute'?: number | null; "Start Minute"?: number | null;
Dauer?: string; Dauer?: string;
'Erste Ausführung'?: string; "Erste Ausführung"?: string;
'Ausführung bis'?: string; "Ausführung bis"?: string;
'Intervall zwischen'?: string; "Intervall zwischen"?: string;
Wochentage?: string; Wochentage?: string;
Sichtbarkeit?: { id: number; value: string; color: string }; Sichtbarkeit?: { id: number; value: string; color: string };
Sortierung?: number; Sortierung?: number;
} }
interface BaserowList<T> {
count: number;
next: string | null;
previous: string | null;
results: T[];
}
/* ── Generic helpers ───────────────────────────────── */ /* ── Generic helpers ───────────────────────────────── */
async function listRows<T>(tableId: string): Promise<T[]> { function assertPositiveInteger(id: number, label: string): void {
const res = await fetch( if (!Number.isInteger(id) || id <= 0) {
`${BASE}/api/database/rows/table/${tableId}/?user_field_names=true&size=200`, throw new Error(`${label} must be a positive integer.`);
{ headers } }
);
if (!res.ok) throw new Error(`Baserow GET failed: ${res.status}`);
const data: BaserowList<T> = await res.json();
return data.results;
} }
async function createRow(tableId: string, fields: Record<string, unknown>) { function removeUndefinedValues(
const res = await fetch( fields: Record<string, unknown>,
`${BASE}/api/database/rows/table/${tableId}/?user_field_names=true`, ): Record<string, unknown> {
{ method: 'POST', headers, body: JSON.stringify(fields) } return Object.fromEntries(
Object.entries(fields).filter(([, value]) => value !== undefined),
); );
if (!res.ok) throw new Error(`Baserow POST failed: ${res.status}`);
return res.json();
} }
async function updateRow(tableId: string, rowId: number, fields: Record<string, unknown>) { async function readErrorMessage(response: Response): Promise<string> {
const res = await fetch( const message = await response.text();
`${BASE}/api/database/rows/table/${tableId}/${rowId}/?user_field_names=true`, return message || response.statusText || "Unknown error";
{ method: 'PATCH', headers, body: JSON.stringify(fields) }
);
if (!res.ok) throw new Error(`Baserow PATCH failed: ${res.status}`);
return res.json();
} }
async function deleteRow(tableId: string, rowId: number) { async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
const res = await fetch( const method = init.method ?? "GET";
`${BASE}/api/database/rows/table/${tableId}/${rowId}/`, const headers = new Headers(init.headers);
{ method: 'DELETE', headers } headers.set("Accept", "application/json");
if (init.body !== undefined && init.body !== null) {
headers.set("Content-Type", "application/json");
}
let response: Response;
try {
response = await fetch(`/api${path}`, {
...init,
headers,
});
} catch (error) {
const message =
error instanceof Error ? error.message : "Unknown network error";
throw new Error(
`Request ${method} ${path} failed (network error): ${message}`,
); );
if (!res.ok) throw new Error(`Baserow DELETE failed: ${res.status}`); }
if (!response.ok) {
const message = await readErrorMessage(response);
throw new Error(
`Request ${method} ${path} failed (${response.status}): ${message}`,
);
}
if (response.status === 204) {
return undefined as T;
}
return (await response.json()) as T;
} }
/* ── Public API ────────────────────────────────────── */ /* ── Public API ────────────────────────────────────── */
export const api = { export const api = {
// Servers getServers: () => request<Server[]>("/servers"),
getServers: () => listRows<Server>(SERVERS_TABLE),
createServer: (name: string, description: string) => createServer: (name: string, description: string) =>
createRow(SERVERS_TABLE, { Name: name, Beschreibung: description }), request<Server>("/servers", {
deleteServer: (id: number) => deleteRow(SERVERS_TABLE, id), method: "POST",
body: JSON.stringify({
// Processes Name: name.trim(),
getProcesses: () => listRows<Process>(PROCESSES_TABLE), Beschreibung: description.trim(),
createProcess: (fields: Record<string, unknown>) => }),
createRow(PROCESSES_TABLE, fields), }),
updateProcess: (id: number, fields: Record<string, unknown>) => { deleteServer: (id: number) => {
// If fields.Sichtbarkeit is undefined, it won't be sent, which protects against the field missing. assertPositiveInteger(id, "Server ID");
const cleanFields = Object.fromEntries(Object.entries(fields).filter(([_, v]) => v !== undefined)); return request<void>(`/servers/${id}`, { method: "DELETE" });
return updateRow(PROCESSES_TABLE, id, cleanFields); },
getProcesses: () => request<Process[]>("/processes"),
createProcess: (fields: Record<string, unknown>) =>
request<Process>("/processes", {
method: "POST",
body: JSON.stringify(fields),
}),
updateProcess: (id: number, fields: Record<string, unknown>) => {
assertPositiveInteger(id, "Process ID");
return request<Process>(`/processes/${id}`, {
method: "PATCH",
body: JSON.stringify(removeUndefinedValues(fields)),
});
},
deleteProcess: (id: number) => {
assertPositiveInteger(id, "Process ID");
return request<void>(`/processes/${id}`, { method: "DELETE" });
}, },
deleteProcess: (id: number) => deleteRow(PROCESSES_TABLE, id),
}; };