Versie 0.2.0

This commit is contained in:
Atreides Assets committed 2026-10-29 11:57:29 +01:00
commit e77d8fd373
495 files changed
+81026

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
// Generated, do not edit. sha256:95f28c26a77b79beac5f8c6818bd25243362641c1d8549949427a847e4f810f9
import { DatabaseSync } from 'node:sqlite';
export const BUSY_TIMEOUT_MS = 5000;
let db = null;
export function openDatabase(pad) {
if (db) throw new Error('A database connection is already open; close it first with closeDatabase().');
db = new DatabaseSync(pad, { enableForeignKeyConstraints: true });
db.exec(`PRAGMA journal_mode = WAL`);
db.exec(`PRAGMA synchronous = NORMAL`);
db.exec(`PRAGMA busy_timeout = ${BUSY_TIMEOUT_MS}`);
return db;
}
export function database() { return db; }
export function withOtherDatabase(pad, fn) {
const vorige = db;
db = null;
try {
openDatabase(pad);
const uit = fn();
if (uit && typeof uit.then === 'function') throw new Error('withOtherDatabase: fn must be synchronous.');
return uit;
} finally {
closeDatabase();
db = vorige;
}
}
export function closeDatabase() {
if (!db) return;
db.close();
db = null;
}
export function pragmaStand() {
const lees = (naam, veld) => database().prepare(`PRAGMA ${naam}`).get()[veld];
return {
foreignKeys: lees('foreign_keys', 'foreign_keys') === 1,
journalMode: lees('journal_mode', 'journal_mode'),
synchronous: lees('synchronous', 'synchronous'),
busyTimeout: lees('busy_timeout', 'timeout'),
};
}
const bind = params => params.map(v => {
if (v === undefined) return null;
if (typeof v === 'boolean') return v ? 1n : 0n;
if (typeof v === 'number' && Number.isInteger(v)) return BigInt(v);
return v;
});
export function run(sql, params) {
if (params === undefined) db.exec(sql);
else db.prepare(sql).run(...bind(params));
}
export function all(sql, params = []) {
return db.prepare(sql).all(...bind(params)).map(rij => ({ ...rij }));
}
export function one(sql, params = []) {
const rijen = all(sql, params);
return rijen.length ? rijen[0] : null;
}
export function lastId() {
const r = one(`SELECT last_insert_rowid() AS id`);
return r ? r.id : null;
}
File diff suppressed because it is too large. Load diff
+1239
View File
File diff suppressed because it is too large. Load diff
+189
View File
@@ -0,0 +1,189 @@
// Generated, do not edit. sha256:52135637bc4779b2b5f755ec50fedf41d2fa7b65d90a35f911c4e68f0b093874
import { CONFIG, PREFERENCES, SOURCE_CAPABILITIES, SOURCE_ENDPOINTS, DASHBOARD } from '../../shared/config.js';
import { toSearchName } from '../../shared/search-name.js';
import { run, one, all, lastId } from './conn.mjs';
function basisMunt() {
const r = one(`SELECT baseCurrency FROM Portfolio WHERE id = 1`);
if (!r) throw new Error('Seeds need the Portfolio row: write the unit of account first.');
return r.baseCurrency;
}
function fiatAssets(basis) {
const codes = [...new Set([basis, 'EUR', 'USD'])];
const namen = new Intl.DisplayNames(['nl-NL'], { type: 'currency' });
return codes.map(code => {
const naam = namen.of(code);
const decimals = new Intl.NumberFormat('nl-NL', { style: 'currency', currency: code }).resolvedOptions().maximumFractionDigits;
return { symbol: code, name: naam.charAt(0).toUpperCase() + naam.slice(1), kind: 'fiat', unit: code,
subunit: code === 'EUR' || code === 'USD' ? 'cent' : null, decimals, displayUnit: 'unit' };
});
}
function startBron(symbol, basis) {
if (symbol === basis) return 'fixed';
if (symbol === 'EUR' || symbol === 'USD') return 'frankfurter';
return { BTC: 'mempool', ETH: 'bitvavo', XAU: 'goldapi', XAG: 'goldapi' }[symbol] ?? null;
}
export function seedAssets() {
for (const a of fiatAssets(basisMunt())) {
if (one(`SELECT 1 AS x FROM Asset WHERE symbol = ?`, [a.symbol])) continue;
run(
`INSERT INTO Asset (symbol, name, searchName, kind, unit, subunit, decimals, displayUnit)
VALUES (?,?,?,?,?,?,?,?)`,
[a.symbol, a.name, toSearchName(a.name), a.kind, a.unit, a.subunit, a.decimals, a.displayUnit]
);
}
}
const SEED_METAL_PRODUCTS = [
{ symbol: 'XAU', name: 'Krugerrand 1 oz', form: 'coin', grams: CONFIG.METALS.GRAMS_PER_TROY_OUNCE },
{ symbol: 'XAU', name: 'Maple Leaf 1 oz', form: 'coin', grams: CONFIG.METALS.GRAMS_PER_TROY_OUNCE },
{ symbol: 'XAU', name: 'Wiener Philharmoniker 1 oz', form: 'coin', grams: CONFIG.METALS.GRAMS_PER_TROY_OUNCE },
{ symbol: 'XAU', name: 'Baar 1 g', form: 'bar', grams: 1 },
{ symbol: 'XAU', name: 'Baar 5 g', form: 'bar', grams: 5 },
{ symbol: 'XAU', name: 'Baar 10 g', form: 'bar', grams: 10 },
{ symbol: 'XAU', name: 'Baar 100 g', form: 'bar', grams: 100 },
{ symbol: 'XAU', name: 'Baar 1 kg', form: 'bar', grams: 1000 },
{ symbol: 'XAG', name: 'Maple Leaf 1 oz', form: 'coin', grams: CONFIG.METALS.GRAMS_PER_TROY_OUNCE },
{ symbol: 'XAG', name: 'Wiener Philharmoniker 1 oz', form: 'coin', grams: CONFIG.METALS.GRAMS_PER_TROY_OUNCE },
{ symbol: 'XAG', name: 'Baar 100 g', form: 'bar', grams: 100 },
{ symbol: 'XAG', name: 'Baar 1 kg', form: 'bar', grams: 1000 },
];
export function seedMetalProducts() {
const factor = Math.pow(10, CONFIG.METALS.BASE_DECIMALS);
for (const p of SEED_METAL_PRODUCTS) {
const a = one(`SELECT id FROM Asset WHERE symbol = ?`, [p.symbol]);
if (!a) continue;
if (one(`SELECT 1 AS x FROM MetalProduct WHERE assetId = ? AND name = ?`, [a.id, p.name])) continue;
run(`INSERT INTO MetalProduct (assetId, name, searchName, form, fineGrams) VALUES (?,?,?,?,?)`,
[a.id, p.name, toSearchName(p.name), p.form, Math.round(p.grams * factor)]);
}
}
const SEED_CONNECTIONS = [
{ kind: 'btc_explorer', name: 'mempool.space (publiek)', url: CONFIG.PRICE_SOURCES.MEMPOOL_PUBLIC_BASE, isActive: 1, isSystem: 1 },
];
export function seedConnections() {
const now = new Date().toISOString();
for (const c of SEED_CONNECTIONS) {
if (one(`SELECT 1 AS x FROM Connection WHERE kind = ? AND name = ?`, [c.kind, c.name])) continue;
const hasActive = !!one(`SELECT 1 AS x FROM Connection WHERE kind = ? AND isActive = 1`, [c.kind]);
run(`INSERT INTO Connection (name, searchName, url, kind, isActive, isSystem, lastStatus, lastCheckedAt, createdAt, updatedAt)
VALUES (?,?,?,?,?,?,NULL,NULL,?,?)`,
[c.name, toSearchName(c.name), c.url, c.kind, hasActive ? 0 : c.isActive, c.isSystem, now, now]);
}
}
function seedProviderList(basis) {
return [
{ key: 'manual', name: 'Handmatig', baseUrl: '', authenticationKind: 'none', authenticationRequired: 0, isSystem: true,
authenticationHeaderName: null, authenticationParameterName: null, hmac: null,
endpoints: [], symbols: [], coverage: [], outputs: [] },
{ key: CONFIG.MARKET_SERVICE.PROVIDER_KEY, name: 'Atreides Services', baseUrl: '', authenticationKind: 'none',
authenticationRequired: 0, isSystem: true, authenticationHeaderName: null, authenticationParameterName: null,
hmac: null, endpoints: [], symbols: [], coverage: [], outputs: [] },
{ key: 'fixed', name: 'Vaste koers', baseUrl: '', authenticationKind: 'none', authenticationRequired: 0, isSystem: true,
authenticationHeaderName: null, authenticationParameterName: null, hmac: null,
fixedRate: { fiatPrice: 1 },
endpoints: [
{ capability: 'spot', method: 'GET', pathTemplate: '', queryTemplate: null },
{ capability: 'history_point', method: 'GET', pathTemplate: '', queryTemplate: null },
],
symbols: [], coverage: [basis], outputs: [] },
];
}
export function seedProviders() {
for (const p of seedProviderList(basisMunt())) {
if (one(`SELECT 1 AS x FROM Provider WHERE key = ?`, [p.key])) continue;
const isActive = p.authenticationRequired ? 0 : 1;
run(`INSERT INTO Provider
(key, name, searchName, baseUrl, authenticationHeaderName, authenticationParameterName,
authenticationKind, authenticationRequired, isActive, isSeed, isSystem, createdAt)
VALUES (?,?,?,?,?,?,?,?,?,1,?,?)`,
[p.key, p.name, toSearchName(p.name), p.baseUrl, p.authenticationHeaderName, p.authenticationParameterName,
p.authenticationKind, p.authenticationRequired, isActive, p.isSystem ? 1 : 0, new Date().toISOString()]);
const pid = lastId();
for (const symbool of p.coverage || []) {
run(`INSERT INTO ProviderCoverage (providerId, symbol) VALUES (?,?)`, [pid, symbool]);
}
if (p.fixedRate) {
run(`INSERT INTO ProviderFixedRate (providerId, fiatPrice) VALUES (?,?)`, [pid, p.fixedRate.fiatPrice]);
}
if (p.callsPerMonth) {
run(`INSERT INTO ProviderCallLimit (providerId, callsPerMonth, createdAt) VALUES (?,?,?)`,
[pid, p.callsPerMonth, new Date().toISOString()]);
}
if (p.hmac) {
const h = p.hmac;
run(`INSERT INTO ProviderAuthenticationHmac
(providerId, messageParts, hashAlgorithm, encoding, keyHeader, signatureHeader, timestampHeader, windowHeader, windowMs)
VALUES (?,?,?,?,?,?,?,?,?)`,
[pid, h.messageParts, h.hashAlgorithm, h.encoding, h.keyHeader, h.signatureHeader, h.timestampHeader, h.windowHeader, h.windowMs]);
}
for (const e of p.endpoints)
run(`INSERT INTO ProviderEndpoint (providerId, capability, method, pathTemplate, queryTemplate, quoteCurrency) VALUES (?,?,?,?,?,?)`,
[pid, e.capability, e.method, e.pathTemplate, e.queryTemplate, e.quoteCurrency ?? null]);
for (const s of p.symbols)
run(`INSERT INTO ProviderInputMap (providerId, symbol, externalId, quoteUnit, isCatchAll) VALUES (?,?,?,?,?)`,
[pid, s.isCatchAll ? null : s.symbol, s.externalId, s.quoteUnit ?? null, s.isCatchAll ? 1 : 0]);
for (const o of p.outputs)
run(`INSERT INTO ProviderOutputMap (providerId, capability, internalField, jsonPath, transform) VALUES (?,?,?,?,?)`,
[pid, o.capability, o.internalField, o.jsonPath, o.transform]);
}
}
export function seedPriceSource(assetId, status) {
const assets = assetId
? all(`SELECT id, symbol FROM Asset WHERE id = ?`, [assetId])
: all(`SELECT id, symbol FROM Asset`);
const basis = basisMunt();
for (const asset of assets) {
const srcKey = startBron(asset.symbol, basis);
if (!srcKey) continue;
const provider = one(`SELECT id FROM Provider WHERE key = ?`, [srcKey]);
if (!provider) continue;
for (const capability of SOURCE_CAPABILITIES) {
if (one(`SELECT 1 AS x FROM PriceSource WHERE assetId = ? AND capability = ?`,
[asset.id, capability])) continue;
const eindpunten = SOURCE_ENDPOINTS[capability];
if (!one(`SELECT 1 AS x FROM ProviderEndpoint WHERE providerId = ? AND capability IN (${eindpunten.map(() => '?').join(',')})`,
[provider.id, ...eindpunten])) continue;
run(`INSERT INTO PriceSource (assetId, capability, providerId, rank, status) VALUES (?,?,?,1,?)`,
[asset.id, capability, provider.id, status]);
}
}
}
export function seedDashboardProfile() {
if (one(`SELECT 1 AS x FROM DashboardProfile WHERE isSystem = 1`)) return false;
const now = new Date().toISOString();
const hasActive = !!one(`SELECT 1 AS x FROM DashboardProfile WHERE isActive = 1`);
run(`INSERT INTO DashboardProfile (name, searchName, description, sortOrder, isActive, isSystem, createdAt, updatedAt)
VALUES (?,?,NULL,0,?,1,?,?)`,
['Standaard', toSearchName('Standaard'), hasActive ? 0 : 1, now, now]);
const profileId = lastId();
for (const w of DASHBOARD.DEFAULT_LAYOUT) {
const def = DASHBOARD.WIDGETS[w.type];
if (!def) continue;
run(`INSERT INTO DashboardWidget (profileId, assetId, instanceKey, gridX, gridY, spanWidth, spanHeight, type, settings)
VALUES (?,NULL,?,?,0,?,NULL,?,NULL)`, [profileId, w.instId, w.x || 0, def.defaultSpan, w.type]);
}
return true;
}
const FRESH_VALUES = Object.freeze({ app: Object.freeze({ market_spot: 'first' }) });
export function seedPreferences() {
for (const [scope, keys] of Object.entries(PREFERENCES)) {
for (const [key, spec] of Object.entries(keys)) {
if (spec.secret) continue;
run(`INSERT INTO Preference (scope, key, value) VALUES (?,?,?)`,
[scope, key, JSON.stringify(FRESH_VALUES[scope]?.[key] ?? spec.initial)]);
}
}
}