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 ─────────────────────────────────────────── */
export interface Server {
@@ -27,78 +17,106 @@ export interface Process {
Farbe?: string;
Start?: string;
Ende?: string;
'Start Minute'?: number | null;
"Start Minute"?: number | null;
Dauer?: string;
'Erste Ausführung'?: string;
'Ausführung bis'?: string;
'Intervall zwischen'?: string;
"Erste Ausführung"?: string;
"Ausführung bis"?: string;
"Intervall zwischen"?: string;
Wochentage?: string;
Sichtbarkeit?: { id: number; value: string; color: string };
Sortierung?: number;
}
interface BaserowList<T> {
count: number;
next: string | null;
previous: string | null;
results: T[];
}
/* ── Generic helpers ───────────────────────────────── */
async function listRows<T>(tableId: string): Promise<T[]> {
const res = await fetch(
`${BASE}/api/database/rows/table/${tableId}/?user_field_names=true&size=200`,
{ headers }
);
if (!res.ok) throw new Error(`Baserow GET failed: ${res.status}`);
const data: BaserowList<T> = await res.json();
return data.results;
function assertPositiveInteger(id: number, label: string): void {
if (!Number.isInteger(id) || id <= 0) {
throw new Error(`${label} must be a positive integer.`);
}
}
async function createRow(tableId: string, fields: Record<string, unknown>) {
const res = await fetch(
`${BASE}/api/database/rows/table/${tableId}/?user_field_names=true`,
{ method: 'POST', headers, body: JSON.stringify(fields) }
function removeUndefinedValues(
fields: Record<string, unknown>,
): Record<string, unknown> {
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>) {
const res = await fetch(
`${BASE}/api/database/rows/table/${tableId}/${rowId}/?user_field_names=true`,
{ method: 'PATCH', headers, body: JSON.stringify(fields) }
);
if (!res.ok) throw new Error(`Baserow PATCH failed: ${res.status}`);
return res.json();
async function readErrorMessage(response: Response): Promise<string> {
const message = await response.text();
return message || response.statusText || "Unknown error";
}
async function deleteRow(tableId: string, rowId: number) {
const res = await fetch(
`${BASE}/api/database/rows/table/${tableId}/${rowId}/`,
{ method: 'DELETE', headers }
async function request<T>(path: string, init: RequestInit = {}): Promise<T> {
const method = init.method ?? "GET";
const headers = new Headers(init.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 ────────────────────────────────────── */
export const api = {
// Servers
getServers: () => listRows<Server>(SERVERS_TABLE),
getServers: () => request<Server[]>("/servers"),
createServer: (name: string, description: string) =>
createRow(SERVERS_TABLE, { Name: name, Beschreibung: description }),
deleteServer: (id: number) => deleteRow(SERVERS_TABLE, id),
// Processes
getProcesses: () => listRows<Process>(PROCESSES_TABLE),
createProcess: (fields: Record<string, unknown>) =>
createRow(PROCESSES_TABLE, fields),
updateProcess: (id: number, fields: Record<string, unknown>) => {
// 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));
return updateRow(PROCESSES_TABLE, id, cleanFields);
request<Server>("/servers", {
method: "POST",
body: JSON.stringify({
Name: name.trim(),
Beschreibung: description.trim(),
}),
}),
deleteServer: (id: number) => {
assertPositiveInteger(id, "Server ID");
return request<void>(`/servers/${id}`, { method: "DELETE" });
},
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),
};