refactor(api): route baserow calls through internal proxy
This commit is contained in:
@@ -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<T> {
|
||||
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<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) }
|
||||
);
|
||||
if (!res.ok) throw new Error(`Baserow POST failed: ${res.status}`);
|
||||
return res.json();
|
||||
function removeUndefinedValues(
|
||||
fields: Record<string, unknown>,
|
||||
): Record<string, unknown> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(fields).filter(([, value]) => value !== undefined),
|
||||
);
|
||||
}
|
||||
|
||||
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 }
|
||||
);
|
||||
if (!res.ok) throw new Error(`Baserow DELETE failed: ${res.status}`);
|
||||
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 (!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),
|
||||
createServer: (name: string, description: string) =>
|
||||
createRow(SERVERS_TABLE, { Name: name, Beschreibung: description }),
|
||||
deleteServer: (id: number) => deleteRow(SERVERS_TABLE, id),
|
||||
getServers: () => request<Server[]>("/servers"),
|
||||
createServer: (name: string, description: string) =>
|
||||
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" });
|
||||
},
|
||||
|
||||
// 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);
|
||||
},
|
||||
deleteProcess: (id: number) => deleteRow(PROCESSES_TABLE, id),
|
||||
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" });
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user