import { service as defaultService } from "../modules/dashboard/service.js"; import { getConfig } from "../common/config.js"; // Same interface as the local service, so it is a drop-in for it. // // Tenant scoping: locally, every call carries the session's own user id. // Remotely, the API KEY is the scope — we deliberately send NO user id, since a // local primary key is meaningless on another operator and must never be // trusted as a cross-operator identity. export function makePassthrough({ service = defaultService, config = getConfig(), fetchImpl = fetch } = {}) { const sources = () => config.dashboard || { core: {}, audit: {} }; const remote = (s) => sources()[s] && sources()[s].url; const federated = () => Boolean(remote("core") || remote("audit")); async function fromSource(source, endpoint, params, localCall) { const src = sources()[source]; if (!src || !src.url) return localCall(); const res = await fetchImpl(`${src.url}/api/v1/data/dashboard/${endpoint}`, { method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${src.key}` }, body: JSON.stringify(params), }); if (!res.ok) throw new Error(`federation: ${source}/${endpoint} -> ${res.status}`); return res.json(); } const num = (v) => Number(v) || 0; function mergeSummary(core, audit) { const cu = core.apiUsage || {}, au = audit.apiUsage || {}; const cs = core.storage || {}, as = audit.storage || {}; const split = (c, a) => ({ core: num(c), audit: num(a), total: num(c) + num(a) }); return { apiUsage: split(cu.core, au.audit), apiKeys: core.apiKeys, endpoints: core.endpoints, storage: { onDisk: split(cs.onDisk?.core, as.onDisk?.audit), logical: split(cs.logical?.core, as.logical?.audit), }, }; } function mergeByDate(coreSeries = [], auditSeries = [], coreKey, auditKey, totalKey) { const auditByDate = new Map(auditSeries.map((r) => [r.date, r])); return coreSeries.map((r) => { const a = auditByDate.get(r.date) || {}; const core = num(r[coreKey]); const audit = num(a[auditKey]); return { date: r.date, [coreKey]: core, [auditKey]: audit, [totalKey]: core + audit }; }); } return { async summary(userId, window) { if (!federated()) return service.summary(userId, window); const [core, audit] = await Promise.all([ fromSource("core", "summary", window, () => service.summary(userId, window)), fromSource("audit", "summary", window, () => service.summary(userId, window)), ]); return mergeSummary(core, audit); }, async transactions(userId, opts) { const source = opts.type === "audit" ? "audit" : "core"; return fromSource(source, "transactions", opts, () => service.transactions(userId, opts)); }, async series(userId, window) { if (!federated()) return service.series(userId, window); const [core, audit] = await Promise.all([ fromSource("core", "series", window, () => service.series(userId, window)), fromSource("audit", "series", window, () => service.series(userId, window)), ]); return { usage: mergeByDate(core.usage, audit.usage, "core", "audit", "total"), storage: mergeByDate(core.storage, audit.storage, "coreDisk", "auditDisk", "totalDisk"), }; }, // Audit ids belong to the audit source that served the listing. async verify(userId, targets) { const { eventUuids = [], auditIds = [] } = targets || {}; const params = { ids: eventUuids.join(","), auditIds: auditIds.join(",") }; return fromSource("audit", "verify", params, () => service.verify(userId, targets)); }, async event(userId, eventUuid) { return fromSource("core", "event", { eventUuid }, () => service.event(userId, eventUuid)); }, async agreement(userId, agreementUuid) { return fromSource("core", "agreement", { agreementUuid }, () => service.agreement(userId, agreementUuid)); }, async details(userId, eventUuid) { return fromSource("core", "details", { eventUuid }, () => service.details(userId, eventUuid)) } }; }