Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user