// 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(); }