101 lines
4.1 KiB
JavaScript
101 lines
4.1 KiB
JavaScript
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))
|
|
}
|
|
};
|
|
}
|