// public/assets/api.js /* English comments: centralized API wrapper */ (function () { function normalizeDateString(value) { if (typeof value !== 'string') return value; const s = value.trim(); if (s === '') return value; const isoDate = /^\d{4}-\d{2}-\d{2}$/; const isoDateTime = /^\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}:\d{2}/; const dotDate = /^\d{2}\.\d{2}\.\d{4}$/; const dotDateTime = /^\d{2}\.\d{2}\.\d{4}\s+\d{2}:\d{2}:\d{2}$/; let v = s; if (isoDate.test(v)) v = `${v} 00:00:00`; if (dotDate.test(v)) { const parts = v.split('.'); v = `${parts[2]}-${parts[1]}-${parts[0]} 00:00:00`; } else if (dotDateTime.test(v)) { const [datePart, timePart] = v.split(/\s+/, 2); const parts = datePart.split('.'); v = `${parts[2]}-${parts[1]}-${parts[0]} ${timePart}`; } if (isoDateTime.test(v)) { const d = new Date(v.replace(' ', 'T') + 'Z'); if (!Number.isNaN(d.getTime())) { return d.toISOString().slice(0, 19).replace('T', ' '); } } return v; } function normalizeDates(payload) { if (payload === null || payload === undefined) return payload; if (Array.isArray(payload)) return payload.map(normalizeDates); if (typeof payload !== 'object') return payload; const out = {}; Object.keys(payload).forEach((key) => { const value = payload[key]; if (value && typeof value === 'object') { out[key] = normalizeDates(value); } else if (typeof value === 'string' && (key === 'ts' || key.endsWith('_at') || key.endsWith('_ts'))) { out[key] = normalizeDateString(value); } else { out[key] = value; } }); return out; } async function request(path, opts = {}) { const headers = { ...(opts.headers || {}) }; const hasBodyObject = opts.body && typeof opts.body === 'object' && !(opts.body instanceof FormData); const body = hasBodyObject ? JSON.stringify(normalizeDates(opts.body)) : opts.body; if (hasBodyObject && !headers['Content-Type']) { headers['Content-Type'] = 'application/json'; } if (window.Auth?.isAccessExpired?.() && window.Auth?.refreshTokens) { await window.Auth.refreshTokens(); } const token = window.Auth?.getAccessToken?.(); if (token) { headers['Authorization'] = `Bearer ${token}`; } let res = await fetch(path, { credentials: 'same-origin', ...opts, headers, body }); if (res.status === 401 && window.Auth?.refreshTokens) { const refreshed = await window.Auth.refreshTokens(); if (refreshed) { const newToken = window.Auth.getAccessToken(); if (newToken) headers['Authorization'] = `Bearer ${newToken}`; res = await fetch(path, { credentials: 'same-origin', ...opts, headers, body }); } else if (window.Auth?.redirectToLogin) { window.Auth.redirectToLogin(); } } const json = await res.json().catch(() => null); if (!json || json.ok !== true) { const msg = json?.error?.message || `Request failed: ${res.status}`; throw new Error(msg); } return json; } window.Api = { request, }; })();