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,104 +1,122 @@
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 {
id: number; id: number;
Name: string; Name: string;
Beschreibung: string; Beschreibung: string;
Sortierung?: number; Sortierung?: number;
Prozesse: { id: number; value: string }[]; Prozesse: { id: number; value: string }[];
} }
export interface Process { export interface Process {
id: number; id: number;
Name: string; Name: string;
Server?: { id: number; value: string }[]; Server?: { id: number; value: string }[];
Status?: { id: number; value: string; color: string }; Status?: { id: number; value: string; color: string };
Wiederholung?: { id: number; value: string; color: string }; Wiederholung?: { id: number; value: string; color: string };
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 (!res.ok) throw new Error(`Baserow DELETE failed: ${res.status}`); 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 (!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) => request<Server>("/servers", {
createRow(SERVERS_TABLE, { Name: name, Beschreibung: description }), method: "POST",
deleteServer: (id: number) => deleteRow(SERVERS_TABLE, id), body: JSON.stringify({
Name: name.trim(),
Beschreibung: description.trim(),
}),
}),
deleteServer: (id: number) => {
assertPositiveInteger(id, "Server ID");
return request<void>(`/servers/${id}`, { method: "DELETE" });
},
// Processes getProcesses: () => request<Process[]>("/processes"),
getProcesses: () => listRows<Process>(PROCESSES_TABLE), createProcess: (fields: Record<string, unknown>) =>
createProcess: (fields: Record<string, unknown>) => request<Process>("/processes", {
createRow(PROCESSES_TABLE, fields), method: "POST",
updateProcess: (id: number, fields: Record<string, unknown>) => { body: JSON.stringify(fields),
// If fields.Sichtbarkeit is undefined, it won't be sent, which protects against the field missing. }),
const cleanFields = Object.fromEntries(Object.entries(fields).filter(([_, v]) => v !== undefined)); updateProcess: (id: number, fields: Record<string, unknown>) => {
return updateRow(PROCESSES_TABLE, id, cleanFields); assertPositiveInteger(id, "Process ID");
}, return request<Process>(`/processes/${id}`, {
deleteProcess: (id: number) => deleteRow(PROCESSES_TABLE, id), method: "PATCH",
body: JSON.stringify(removeUndefinedValues(fields)),
});
},
deleteProcess: (id: number) => {
assertPositiveInteger(id, "Process ID");
return request<void>(`/processes/${id}`, { method: "DELETE" });
},
}; };