// Generated, do not edit. sha256:6b0db04ca1e019c207aa13f2f0133b94c5f5783d9d7742ec8001cff39b53d404 import * as custom from './price/custom.js'; import { getKeys, setKeys, keyStatus } from './keys.mjs'; import * as market from './market-service.mjs'; import { handleDataApi } from './data-api.mjs'; import { recordIntegrationCall } from './data/integration-log.js'; import { mcpClientConfig } from './mcp/client-config.mjs'; import { dryRunTool, dryRunTools } from './mcp/dry-run.mjs'; import { exportDatabase } from './db-export.mjs'; import { exportPriceHistory } from './price-export.mjs'; import { exportIntegrations } from './integrations-export.mjs'; import { restoreDatabase } from './db-restore.mjs'; import { startFreshDatabase, chooseSetupCurrency } from './db-fresh.mjs'; import { createBackup, restoreBackup, isBackupBestand } from './db-backup.mjs'; import { overviewPortfolios, createPortfolio, renamePortfolio, archivePortfolio, purgeArchived, activatePortfolio, } from './portfolios-admin.mjs'; import { credentialsStatus, setCredentials, clearCredentials } from './auth.mjs'; import { closeDatabase } from './db.js'; import { apiRoutes, services } from './registers.mjs'; import { CONFIG } from '../shared/config.js'; import { AppError, errorPayload } from '../shared/app-error.js'; function stopVoorWissel() { try { closeDatabase(); } catch { } process.exit(CONFIG.PORTFOLIO.RESTART_EXIT_CODE); } function stopVoorSluiten() { services.get('kiosk')?.close(); try { closeDatabase(); } catch { } process.exit(0); } const isLoopback = (req) => ['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(req.socket?.remoteAddress); const recordedBrowser = () => services.get('kiosk')?.recorded() ?? null; function sendJson(res, status, obj) { res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'X-Content-Type-Options': 'nosniff' }); res.end(JSON.stringify(obj)); } const sendFout = (res, status, e) => sendJson(res, status, errorPayload(e)); function readRawBody(req, max = 100 * 1024 * 1024) { return new Promise((resolve, reject) => { let len = 0; const chunks = []; req.on('data', c => { len += c.length; if (len > max) { req.destroy(); reject(new AppError('common.fileTooLarge', null, 'File too large.')); } else chunks.push(c); }); req.on('end', () => resolve(Buffer.concat(chunks))); req.on('error', reject); }); } export function readJsonBody(req, max = 256 * 1024) { return new Promise((resolve, reject) => { let len = 0; const chunks = []; req.on('data', c => { len += c.length; if (len > max) { req.destroy(); reject(new Error('Body te groot.')); } else chunks.push(c); }); req.on('end', () => { const raw = Buffer.concat(chunks).toString('utf-8'); if (!raw) return resolve({}); try { resolve(JSON.parse(raw)); } catch { reject(new Error('Invalid JSON.')); } }); req.on('error', reject); }); } export async function handleApi(req, res, url) { const u = new URL(url, 'http://localhost'); const path = u.pathname; const q = u.searchParams; if (path === '/api/health') { sendJson(res, 200, { ok: true }); return true; } if (path === '/api/app/window' && req.method === 'GET') { sendJson(res, 200, { canClose: isLoopback(req) && recordedBrowser() !== null }); return true; } if (path === '/api/app/close' && req.method === 'POST') { if (!isLoopback(req) || !recordedBrowser()) { sendFout(res, 409, new AppError('appWindow.closeUnavailable', null, 'Closing is only possible on this machine, in the app window the app started.')); return true; } res.on('finish', stopVoorSluiten); sendJson(res, 200, { closing: true }); return true; } if (path.startsWith('/api/data/')) return handleDataApi(req, res, url); if (path === '/api/mcp/config' && req.method === 'GET') { sendJson(res, 200, mcpClientConfig()); return true; } if (path === '/api/mcp/tools' && req.method === 'GET') { sendJson(res, 200, { tools: dryRunTools() }); return true; } if (path === '/api/mcp/dry-run' && req.method === 'POST') { try { const body = await readJsonBody(req); sendJson(res, 200, await dryRunTool(body.tool, body.arguments)); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/db/export' && req.method === 'GET') { try { await exportDatabase(res); } catch (e) { sendFout(res, 500, e); } return true; } if (path === '/api/price-history/export' && req.method === 'GET') { try { exportPriceHistory(res); } catch (e) { sendFout(res, 500, e); } return true; } if (path === '/api/integrations/export' && req.method === 'GET') { try { exportIntegrations(res); } catch (e) { sendFout(res, 500, e); } return true; } if (path === '/api/db/backup' && req.method === 'POST') { try { const { passphrase } = await readJsonBody(req); const { bytes, db, secrets } = await createBackup(passphrase); const d = new Date(); const p = n => String(n).padStart(2, '0'); const naam = `assetapp-${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}.assetbak`; res.writeHead(200, { 'Content-Type': 'application/octet-stream', 'Content-Disposition': `attachment; filename="${naam}"`, 'Content-Length': bytes.length, 'X-Backup-Db-Bytes': String(db), 'X-Backup-Secrets-Bytes': String(secrets), 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff', }); res.end(bytes); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/db/import' && req.method === 'POST') { try { const bytes = await readRawBody(req); const uit = isBackupBestand(bytes) ? await restoreBackup(bytes, req.headers['x-backup-passphrase'] || '') : await restoreDatabase(bytes); sendJson(res, 200, { ...uit, versleuteld: isBackupBestand(bytes) }); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/db/fresh' && req.method === 'POST') { try { sendJson(res, 200, await startFreshDatabase()); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/setup/currency' && req.method === 'POST') { try { sendJson(res, 200, await chooseSetupCurrency((await readJsonBody(req)).baseCurrency)); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/portfolios' && req.method === 'GET') { try { sendJson(res, 200, await overviewPortfolios()); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/portfolios/activate' && req.method === 'POST') { try { const uit = activatePortfolio((await readJsonBody(req)).naam); if (uit.gewisseld) res.on('finish', stopVoorWissel); sendJson(res, 200, uit); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/portfolios/create' && req.method === 'POST') { try { const { naam, kopieVan, baseCurrency } = await readJsonBody(req); sendJson(res, 200, await createPortfolio(naam, { kopieVan: kopieVan || null, baseCurrency: baseCurrency || null })); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/portfolios/rename' && req.method === 'POST') { try { const { van, naar } = await readJsonBody(req); sendJson(res, 200, await renamePortfolio(van, naar)); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/portfolios/archive' && req.method === 'POST') { try { sendJson(res, 200, await archivePortfolio((await readJsonBody(req)).naam)); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/portfolios/purge' && req.method === 'POST') { try { sendJson(res, 200, await purgeArchived((await readJsonBody(req)).naam)); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/auth/status' && req.method === 'GET') { try { sendJson(res, 200, await credentialsStatus()); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/auth/set' && req.method === 'POST') { try { const { password } = await readJsonBody(req); sendJson(res, 200, await setCredentials(password)); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/auth/clear' && req.method === 'POST') { try { sendJson(res, 200, await clearCredentials()); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/price/call' && req.method === 'POST') { let provider = null, capability = null, symbol = null, creds = null; const t0 = performance.now(); try { const body = await readJsonBody(req); ({ provider, capability, symbol } = body); const { date, with_raw } = body; if (!provider || !provider.key) throw new Error('The provider config is missing.'); creds = await getKeys(provider.key); sendJson(res, 200, await custom.priceCall(provider, capability, symbol, date, creds, { withRaw: !!with_raw })); } catch (e) { if (provider?.key) { try { await recordIntegrationCall(custom.failureEntry(provider, capability, symbol, e, performance.now() - t0), { secrets: Object.values(creds || {}).filter(v => typeof v === 'string') }); } catch { } } sendFout(res, 502, e); } return true; } if (path === '/api/keys/status' && req.method === 'GET') { try { sendJson(res, 200, await keyStatus(q.get('provider'), q.get('authenticationKind'))); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/keys/set' && req.method === 'POST') { try { const { provider, authenticationKind, values } = await readJsonBody(req); sendJson(res, 200, await setKeys(provider, authenticationKind, values)); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/market/status' && req.method === 'GET') { try { sendJson(res, 200, await market.marketServiceStatus()); } catch (e) { sendFout(res, 400, e); } return true; } if (path === '/api/market/test' && req.method === 'POST') { try { sendJson(res, 200, await market.testMarketService()); } catch (e) { sendFout(res, 502, e); } return true; } const route = apiRoutes.list().find(r => r.path === path && r.method === req.method); if (route) { try { const body = req.method === 'POST' ? await readJsonBody(req) : {}; sendJson(res, 200, await route.run({ q, body })); } catch (e) { sendFout(res, route.errorStatus ?? 400, e); } return true; } return false; }