diff --git a/src/api/baserow.ts b/src/api/baserow.ts index de4eb71..69c63f5 100644 --- a/src/api/baserow.ts +++ b/src/api/baserow.ts @@ -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 ─────────────────────────────────────────── */ export interface Server { - id: number; - Name: string; - Beschreibung: string; - Sortierung?: number; - Prozesse: { id: number; value: string }[]; + id: number; + Name: string; + Beschreibung: string; + Sortierung?: number; + Prozesse: { id: number; value: string }[]; } export interface Process { - id: number; - Name: string; - Server?: { id: number; value: string }[]; - Status?: { id: number; value: string; color: string }; - Wiederholung?: { id: number; value: string; color: string }; - Farbe?: string; - Start?: string; - Ende?: string; - 'Start Minute'?: number | null; - Dauer?: 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 { - count: number; - next: string | null; - previous: string | null; - results: T[]; + id: number; + Name: string; + Server?: { id: number; value: string }[]; + Status?: { id: number; value: string; color: string }; + Wiederholung?: { id: number; value: string; color: string }; + Farbe?: string; + Start?: string; + Ende?: string; + "Start Minute"?: number | null; + Dauer?: string; + "Erste Ausführung"?: string; + "Ausführung bis"?: string; + "Intervall zwischen"?: string; + Wochentage?: string; + Sichtbarkeit?: { id: number; value: string; color: string }; + Sortierung?: number; } /* ── Generic helpers ───────────────────────────────── */ -async function listRows(tableId: string): Promise { - 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 = 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) { - const res = await fetch( - `${BASE}/api/database/rows/table/${tableId}/?user_field_names=true`, - { method: 'POST', headers, body: JSON.stringify(fields) } - ); - if (!res.ok) throw new Error(`Baserow POST failed: ${res.status}`); - return res.json(); +function removeUndefinedValues( + fields: Record, +): Record { + return Object.fromEntries( + Object.entries(fields).filter(([, value]) => value !== undefined), + ); } -async function updateRow(tableId: string, rowId: number, fields: Record) { - 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 { + 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 } - ); - if (!res.ok) throw new Error(`Baserow DELETE failed: ${res.status}`); +async function request(path: string, init: RequestInit = {}): Promise { + 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 (!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(SERVERS_TABLE), - createServer: (name: string, description: string) => - createRow(SERVERS_TABLE, { Name: name, Beschreibung: description }), - deleteServer: (id: number) => deleteRow(SERVERS_TABLE, id), + getServers: () => request("/servers"), + createServer: (name: string, description: string) => + request("/servers", { + method: "POST", + body: JSON.stringify({ + Name: name.trim(), + Beschreibung: description.trim(), + }), + }), + deleteServer: (id: number) => { + assertPositiveInteger(id, "Server ID"); + return request(`/servers/${id}`, { method: "DELETE" }); + }, - // Processes - getProcesses: () => listRows(PROCESSES_TABLE), - createProcess: (fields: Record) => - createRow(PROCESSES_TABLE, fields), - updateProcess: (id: number, fields: Record) => { - // 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); - }, - deleteProcess: (id: number) => deleteRow(PROCESSES_TABLE, id), + getProcesses: () => request("/processes"), + createProcess: (fields: Record) => + request("/processes", { + method: "POST", + body: JSON.stringify(fields), + }), + updateProcess: (id: number, fields: Record) => { + assertPositiveInteger(id, "Process ID"); + return request(`/processes/${id}`, { + method: "PATCH", + body: JSON.stringify(removeUndefinedValues(fields)), + }); + }, + deleteProcess: (id: number) => { + assertPositiveInteger(id, "Process ID"); + return request(`/processes/${id}`, { method: "DELETE" }); + }, };