// assets/js/api.js const API = "api.php"; /** ---- intern: Hilfen ---- */ function withTs(url) { const sep = url.includes("?") ? "&" : "?"; return `${url}${sep}t=${Date.now()}`; // no-store Absicherung } async function parseJsonSafe(res) { const text = await res.text(); try { return JSON.parse(text); } catch (e) { console.error("API: invalid JSON", { status: res.status, text }); return null; } } // ...ss oberer Teil unverändert ... /** zentraler Fetch-Wrapper: Credentials, no-store, 401→Login */ async function apiFetch(url, init = {}) { const res = await fetch(withTs(url), { credentials: "include", cache: "no-store", ...init, }); if (res.status === 401) { window.location.href = "/login.php"; throw new Error("unauthorized"); } return res; } /** ---- Public API ---- */ /** * Action-Call: * - apiAction('auth.me') * - apiAction('sections.list', { method:'GET', data:{ template_id: 123 } }) * - apiAction('templates.create', { method:'POST', data:{ name:'...' } }) */ export async function apiAction( action, { method = "GET", data = null, headers = {} } = {} ) { let url = `${API}?action=${encodeURIComponent(action)}`; const init = { method, headers: { ...headers } }; // GET/HEAD → data als Query-String anhängen (kein Body!) if ((method === "GET" || method === "HEAD") && data && typeof data === "object") { const params = new URLSearchParams(); for (const [k, v] of Object.entries(data)) { if (v !== undefined && v !== null) params.append(k, String(v)); } const qs = params.toString(); if (qs) url += `&${qs}`; } else if (data != null) { init.headers["Content-Type"] = "application/json"; init.body = JSON.stringify(data); } const res = await apiFetch(url, init); return await parseJsonSafe(res); } // ... Rest (apiList, apiCreate, apiUpdate, apiDelete, toast) unverändert ... /** * Listen-Helper für Ressourcen – ruft `${res}.list` auf. * Optional kannst du query-Objekte mitgeben, z.B. { template_id: 123 } für sections. */ export async function apiList(res, query = {}) { const q = new URLSearchParams(query); const qs = q.toString() ? `&${q.toString()}` : ""; const r = await apiAction(`${res}.list`, { method: "GET" }); // Falls du query serverseitig brauchst (z.B. template_id), nutze eine Action-Variante: // return await apiAction(`${res}.list`, { method:"GET", data: query }); return r?.items ?? []; } /** GET by id: nur nutzen, wenn du eine `${res}.get`-Action hast */ export async function apiGet(res, id) { return await apiAction(`${res}.get`, { method: "GET", data: { id } }); } /** CREATE / UPDATE / DELETE – sprechen `${res}.create|update|delete` an */ export async function apiCreate(res, payload) { return await apiAction(`${res}.create`, { method: "POST", data: payload }); } export async function apiUpdate(res, id, payload) { return await apiAction(`${res}.update`, { method: "POST", data: { id, ...payload } }); } export async function apiDelete(res, id) { return await apiAction(`${res}.delete`, { method: "POST", data: { id } }); } /** optionaler Toast-Fallback (keine harte Abhängigkeit) */ export function toast(msg, ok = true, opts = {}) { if (window.Toast?.show) { window.Toast.show(msg, { type: ok ? "success" : "error", duration: 2200, ...opts }); } else { (ok ? console.log : console.error)(msg); } }