Files

280 lines
14 KiB
JavaScript
Raw Permalink Normal View History

2026-10-29 11:57:29 +01:00
// Generated, do not edit. sha256:1db816dc6af267a04206f23a388019cbe76689adb83e89843b65ed1a797537a8
import * as db from '../db.js';
import { toSearchName } from '../../shared/search-name.js';
import { AUTH_KINDS, CAPABILITIES, ENUMS, HTTP_METHODS } from '../../shared/config.js';
import { assertEnum, nowIso } from './_shared.js';
import { emitChange } from './_events.js';
import { fail } from '../../shared/app-error.js';
export function emitProvidersChanged() {
emitChange('providers-changed');
}
export function listProviders() {
return db.all(`SELECT * FROM Provider ORDER BY isSeed DESC, name`);
}
export function getProvider(key) {
const p = db.one(`SELECT * FROM Provider WHERE key = ?`, [key]);
if (!p) return null;
return {
...p,
coverage: db.all(`SELECT symbol FROM ProviderCoverage WHERE providerId = ? ORDER BY symbol`, [p.id]).map(r => r.symbol),
hmac: db.one(`SELECT * FROM ProviderAuthenticationHmac WHERE providerId = ?`, [p.id]) || null,
fixedRate: db.one(`SELECT fiatPrice FROM ProviderFixedRate WHERE providerId = ?`, [p.id]) || null,
callsPerMonth: db.one(`SELECT callsPerMonth FROM ProviderCallLimit WHERE providerId = ?`, [p.id])?.callsPerMonth ?? null,
endpoints: db.all(`SELECT id, capability, method, pathTemplate, queryTemplate, quoteCurrency, minIntervalSeconds FROM ProviderEndpoint WHERE providerId = ?`, [p.id]),
symbols: db.all(`SELECT id, symbol, externalId, quoteUnit, isCatchAll FROM ProviderInputMap WHERE providerId = ?
ORDER BY isCatchAll, symbol`, [p.id]),
outputs: db.all(`SELECT id, capability, internalField, jsonPath, transform FROM ProviderOutputMap WHERE providerId = ?`, [p.id]),
};
}
export function providerCapabilityMap() {
const m = new Map();
for (const r of db.all(`SELECT providerId, capability FROM ProviderEndpoint`)) {
if (!m.has(r.providerId)) m.set(r.providerId, new Set());
m.get(r.providerId).add(r.capability);
}
return m;
}
function assertAuthKind(kind) {
if (!Object.keys(AUTH_KINDS).includes(kind))
throw new Error(`Invalid authenticationKind: '${kind}'. Allowed: ${Object.keys(AUTH_KINDS).join(', ')}.`);
}
function bit01(v, name) {
const n = Number(v);
if (n !== 0 && n !== 1) throw new Error(`Invalid value for ${name}: ${v} (0 or 1 expected).`);
return n;
}
function toCoverageList(v) {
const arr = Array.isArray(v) ? v : String(v ?? '').split(',');
return [...new Set(arr.map(s => String(s).trim()).filter(Boolean))];
}
function schrijfCoverage(providerId, symbolen) {
db.run(`DELETE FROM ProviderCoverage WHERE providerId = ?`, [providerId]);
for (const symbool of symbolen) {
db.run(`INSERT INTO ProviderCoverage (providerId, symbol) VALUES (?,?)`, [providerId, symbool]);
}
}
function keyFromName(name) {
const basis = String(name).normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase()
.replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '') || 'integratie';
let key = basis;
for (let n = 2; db.one(`SELECT 1 AS x FROM Provider WHERE key = ?`, [key]); n++) key = `${basis}_${n}`;
return key;
}
export async function createProvider(cfg) {
if (!String(cfg.name || '').trim()) fail('integrations.nameRequired', null, 'A provider name is required.');
assertAuthKind(cfg.authenticationKind);
const key = String(cfg.key || '').trim() || keyFromName(cfg.name);
if (db.one(`SELECT 1 AS x FROM Provider WHERE key = ?`, [key])) {
fail('integrations.keyInUse', { key }, `Provider '${key}' already exists.`);
}
db.run(`INSERT INTO Provider
(key, name, searchName, baseUrl, authenticationHeaderName, authenticationParameterName,
authenticationKind, authenticationRequired, isActive, isSeed, isSystem, createdAt)
VALUES (?,?,?,?,?,?,?,?,?,0,0,?)`,
[key, cfg.name.trim(), toSearchName(cfg.name), cfg.baseUrl ?? '',
cfg.authenticationHeaderName ?? null, cfg.authenticationParameterName ?? null,
cfg.authenticationKind, bit01(cfg.authenticationRequired, 'authenticationRequired'),
bit01(cfg.isActive ?? 1, 'isActive'), nowIso()]);
const pid = db.lastId();
schrijfCoverage(pid, toCoverageList(cfg.coverage));
await db.persist();
emitProvidersChanged();
return pid;
}
export async function updateProvider(key, cfg) {
const p = db.one(`SELECT * FROM Provider WHERE key = ?`, [key]);
if (!p) fail('integrations.unknownProvider', { key }, `Provider '${key}' does not exist.`);
assertAuthKind(cfg.authenticationKind);
db.run(`UPDATE Provider SET name=?, searchName=?, baseUrl=?, authenticationHeaderName=?,
authenticationParameterName=?, authenticationKind=?, authenticationRequired=?, isActive=? WHERE id=?`,
[cfg.name, toSearchName(cfg.name), cfg.baseUrl ?? '',
cfg.authenticationHeaderName ?? null, cfg.authenticationParameterName ?? null,
cfg.authenticationKind, bit01(cfg.authenticationRequired, 'authenticationRequired'),
bit01(cfg.isActive ?? p.isActive, 'isActive'), p.id]);
if (cfg.coverage !== undefined) schrijfCoverage(p.id, toCoverageList(cfg.coverage));
await db.persist();
emitProvidersChanged();
}
export async function setProviderActive(key, isActive) {
const p = db.one(`SELECT id FROM Provider WHERE key = ?`, [key]);
if (!p) fail('integrations.unknownProvider', { key }, `Provider '${key}' does not exist.`);
db.run(`UPDATE Provider SET isActive = ? WHERE id = ?`, [bit01(isActive, 'isActive'), p.id]);
await db.persist();
emitProvidersChanged();
}
export async function setProviderCallLimit(key, callsPerMonth) {
const p = db.one(`SELECT id FROM Provider WHERE key = ?`, [key]);
if (!p) fail('integrations.unknownProvider', { key }, `Provider '${key}' does not exist.`);
if (callsPerMonth == null || String(callsPerMonth).trim() === '') {
db.run(`DELETE FROM ProviderCallLimit WHERE providerId = ?`, [p.id]);
} else {
const n = Number(callsPerMonth);
if (!Number.isInteger(n) || n <= 0) {
fail('integrations.callLimitInvalid', { waarde: callsPerMonth }, `A monthly limit must be a whole number above zero, got '${callsPerMonth}'.`);
}
db.run(`INSERT INTO ProviderCallLimit (providerId, callsPerMonth, createdAt) VALUES (?, ?, ?)
ON CONFLICT(providerId) DO UPDATE SET callsPerMonth = excluded.callsPerMonth`,
[p.id, n, new Date().toISOString()]);
}
await db.persist();
}
export async function deleteProvider(key) {
const p = db.one(`SELECT id, isSystem FROM Provider WHERE key = ?`, [key]);
if (!p) fail('integrations.unknownProvider', { key }, `Provider '${key}' does not exist.`);
if (p.isSystem) throw new Error(`Provider '${key}' is a system row and cannot be deleted.`);
const used = db.one(`SELECT COUNT(*) AS n FROM PriceSource WHERE providerId = ?`, [p.id]).n;
if (used > 0) {
fail('integrations.providerInMatrix', { key, n: used },
`Provider '${key}' is still in the source matrix (${used} choice(s)).`);
}
const prijsrijen = db.one(`SELECT COUNT(*) AS n FROM PriceHistory WHERE providerId = ?`, [p.id]).n;
if (prijsrijen > 0) {
fail('integrations.providerHasPrices', { key, n: prijsrijen },
`Provider '${key}' still has ${prijsrijen} price row(s) in the history.`);
}
db.run(`DELETE FROM Provider WHERE id = ?`, [p.id]);
await db.persist();
emitProvidersChanged();
}
function assertProviderId(pid) {
if (!db.one(`SELECT 1 AS x FROM Provider WHERE id = ?`, [pid]))
throw new Error(`Provider ${pid} does not exist.`);
}
function reqStr(value, name) {
const s = String(value ?? '').trim();
if (!s) fail('integrations.fieldRequired', { veld: name }, `${name} is required.`);
return s;
}
function quoteCurrency(value) {
const s = String(value ?? '').trim().toUpperCase();
if (!s) return null;
if (!/^[A-Z]{3}$/.test(s)) fail('integrations.quoteCurrencyInvalid', { waarde: value }, `Quote currency '${value}' is not a three-letter code.`);
return s;
}
function minInterval(value) {
if (value == null || String(value).trim() === '') return null;
const n = Number(value);
if (!Number.isInteger(n) || n <= 0) fail('integrations.minIntervalInvalid', { waarde: value }, `Minimum interval '${value}' is not a whole number of seconds above zero.`);
return n;
}
export async function createProviderEndpoint(providerId, e) {
assertProviderId(providerId);
assertEnum('capability', CAPABILITIES, e.capability);
assertEnum('method', HTTP_METHODS, e.method); reqStr(e.pathTemplate, 'pathTemplate');
db.run(`INSERT INTO ProviderEndpoint (providerId, capability, method, pathTemplate, queryTemplate, quoteCurrency, minIntervalSeconds, createdAt)
VALUES (?,?,?,?,?,?,?,?)`,
[providerId, e.capability, e.method, e.pathTemplate, e.queryTemplate ?? null, quoteCurrency(e.quoteCurrency),
minInterval(e.minIntervalSeconds), new Date().toISOString()]);
const id = db.lastId(); await db.persist(); return id;
}
export async function updateProviderEndpoint(id, e) {
assertEnum('capability', CAPABILITIES, e.capability);
assertEnum('method', HTTP_METHODS, e.method); reqStr(e.pathTemplate, 'pathTemplate');
if (!db.one(`SELECT 1 AS x FROM ProviderEndpoint WHERE id = ?`, [id])) fail('integrations.unknownEndpoint', { id }, `Endpoint ${id} does not exist.`);
db.run(`UPDATE ProviderEndpoint SET capability=?, method=?, pathTemplate=?, queryTemplate=? WHERE id=?`,
[e.capability, e.method, e.pathTemplate, e.queryTemplate ?? null, id]);
if (Object.prototype.hasOwnProperty.call(e, 'quoteCurrency')) {
db.run(`UPDATE ProviderEndpoint SET quoteCurrency = ? WHERE id = ?`, [quoteCurrency(e.quoteCurrency), id]);
}
if (Object.prototype.hasOwnProperty.call(e, 'minIntervalSeconds')) {
db.run(`UPDATE ProviderEndpoint SET minIntervalSeconds = ? WHERE id = ?`, [minInterval(e.minIntervalSeconds), id]);
}
await db.persist();
}
export async function deleteProviderEndpoint(id) {
db.run(`DELETE FROM ProviderEndpoint WHERE id = ?`, [id]);
await db.persist();
}
function quoteUnit(v) {
if (v == null || String(v).trim() === '') return null;
const u = String(v).trim();
if (!ENUMS.QUOTE_UNIT.includes(u)) fail('integrations.quoteUnitInvalid', { waarde: u }, `Unknown quote unit '${u}'.`);
return u;
}
function assertEenCatchAll(providerId, behalveId = null) {
const r = db.one(`SELECT id FROM ProviderInputMap WHERE providerId = ? AND isCatchAll = 1 AND id IS NOT ?`, [providerId, behalveId]);
if (r) fail('integrations.catchAllExists', null, 'This provider already has a catch-all row.');
}
export async function createProviderSymbol(providerId, s) {
assertProviderId(providerId);
const catchAll = !!s.isCatchAll;
if (catchAll) assertEenCatchAll(providerId); else reqStr(s.symbol, 'symbol');
reqStr(s.externalId, 'externalId');
db.run(`INSERT INTO ProviderInputMap (providerId, symbol, externalId, quoteUnit, isCatchAll, createdAt) VALUES (?,?,?,?,?,?)`,
[providerId, catchAll ? null : s.symbol, s.externalId, quoteUnit(s.quoteUnit), catchAll ? 1 : 0, nowIso()]);
const id = db.lastId(); await db.persist(); return id;
}
export async function updateProviderSymbol(id, s) {
const rij = db.one(`SELECT providerId, isCatchAll FROM ProviderInputMap WHERE id = ?`, [id]);
if (!rij) fail('integrations.unknownSymbolMap', { id }, `Symbol map ${id} does not exist.`);
const catchAll = Object.hasOwn(s, 'isCatchAll') ? !!s.isCatchAll : !!rij.isCatchAll;
if (catchAll) assertEenCatchAll(rij.providerId, id); else reqStr(s.symbol, 'symbol');
reqStr(s.externalId, 'externalId');
db.run(`UPDATE ProviderInputMap SET symbol=?, externalId=?, isCatchAll=? WHERE id=?`,
[catchAll ? null : s.symbol, s.externalId, catchAll ? 1 : 0, id]);
if (Object.hasOwn(s, 'quoteUnit')) db.run(`UPDATE ProviderInputMap SET quoteUnit=? WHERE id=?`, [quoteUnit(s.quoteUnit), id]);
await db.persist();
}
export async function deleteProviderSymbol(id) {
db.run(`DELETE FROM ProviderInputMap WHERE id = ?`, [id]);
await db.persist();
}
export async function createProviderOutput(providerId, o) {
assertProviderId(providerId);
assertEnum('capability', CAPABILITIES, o.capability);
reqStr(o.internalField, 'internalField'); reqStr(o.jsonPath, 'jsonPath'); reqStr(o.transform, 'transform');
db.run(`INSERT INTO ProviderOutputMap (providerId, capability, internalField, jsonPath, transform) VALUES (?,?,?,?,?)`,
[providerId, o.capability, o.internalField, o.jsonPath, o.transform]);
const id = db.lastId(); await db.persist(); return id;
}
export async function updateProviderOutput(id, o) {
assertEnum('capability', CAPABILITIES, o.capability);
reqStr(o.internalField, 'internalField'); reqStr(o.jsonPath, 'jsonPath'); reqStr(o.transform, 'transform');
if (!db.one(`SELECT 1 AS x FROM ProviderOutputMap WHERE id = ?`, [id])) fail('integrations.unknownOutputMap', { id }, `Output map ${id} does not exist.`);
db.run(`UPDATE ProviderOutputMap SET capability=?, internalField=?, jsonPath=?, transform=? WHERE id=?`,
[o.capability, o.internalField, o.jsonPath, o.transform, id]);
await db.persist();
}
export async function deleteProviderOutput(id) {
db.run(`DELETE FROM ProviderOutputMap WHERE id = ?`, [id]);
await db.persist();
}
export async function setProviderHmac(providerId, h) {
assertProviderId(providerId);
reqStr(h.messageParts, 'messageParts'); reqStr(h.hashAlgorithm, 'hashAlgorithm'); reqStr(h.encoding, 'encoding');
reqStr(h.keyHeader, 'keyHeader'); reqStr(h.signatureHeader, 'signatureHeader'); reqStr(h.timestampHeader, 'timestampHeader');
db.run(`INSERT INTO ProviderAuthenticationHmac
(providerId, messageParts, hashAlgorithm, encoding, keyHeader, signatureHeader, timestampHeader, windowHeader, windowMs)
VALUES (?,?,?,?,?,?,?,?,?)
ON CONFLICT(providerId) DO UPDATE SET
messageParts=excluded.messageParts, hashAlgorithm=excluded.hashAlgorithm, encoding=excluded.encoding,
keyHeader=excluded.keyHeader, signatureHeader=excluded.signatureHeader,
timestampHeader=excluded.timestampHeader, windowHeader=excluded.windowHeader, windowMs=excluded.windowMs`,
[providerId, h.messageParts, h.hashAlgorithm, h.encoding, h.keyHeader, h.signatureHeader, h.timestampHeader, h.windowHeader ?? null, h.windowMs ?? null]);
await db.persist();
}
export async function clearProviderHmac(providerId) {
db.run(`DELETE FROM ProviderAuthenticationHmac WHERE providerId = ?`, [providerId]);
await db.persist();
}