openvibers/openvibe.network Commits

T2: SUM() over bigint columns is cast back to bigint

OP OpenVibers committed 29 Sept 2026 · d811796d5c · 4 files, +4 −4

PostgreSQL's SUM(bigint) is numeric, which the driver returns as a string, unlike every other
count and id. The frame ranking added those per host (0 + "12" concatenates), and a replayed
wallet mutation answered its balance as a string. Cast at the four sites: the wallet balance after
a transaction, the frame ranking's hits per host, the admin analytics beacon fallback, and the
developer quota usage.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015D6o3YDeuET1msz98LnHYT

Changes

ddiff --git a/server/admin/analytics-routes.js b/server/admin/analytics-routes.js
iindex 2e33ed4e..11dc34a0 100644
---- a/server/admin/analytics-routes.js
++++ b/server/admin/analytics-routes.js
@@@ -40,7 +40,7 @@ module.exports = function createAnalyticsRoutes(analytics, requireAuth, config,
async function beaconFallback(svc, days) {
try {
const host = BEACON_HOSTS[svc.name]; if (!host) return null;
-- const row = await analytics.db.prepare("SELECT COALESCE(SUM(hits), 0) AS n FROM frame_hits WHERE day >= substring(datetime('now', ?), 1, 10) AND (host = ? OR host LIKE ?)").get(`-${Math.min(days || 30, 365)} days`, host, '%.' + host);
++ const row = await analytics.db.prepare("SELECT COALESCE(SUM(hits), 0)::bigint AS n FROM frame_hits WHERE day >= substring(datetime('now', ?), 1, 10) AND (host = ? OR host LIKE ?)").get(`-${Math.min(days || 30, 365)} days`, host, '%.' + host);
return { ok: true, analytics: { summary: { total_pageviews: row.n, source: 'navbar page-view count (no service analytics)' }, realtime: {} } };
} catch { return null; }
}
ddiff --git a/server/coins/wallet.js b/server/coins/wallet.js
iindex e8bb43f0..a186ad03 100644
---- a/server/coins/wallet.js
++++ b/server/coins/wallet.js
@@@ -44,7 +44,7 @@ async function getBalance(db, userId) {
async function balanceAfterTx(db, tx) {
const row = await db.prepare(
-- 'SELECT COALESCE(SUM(delta), 0) AS bal FROM coin_transactions WHERE user_id = ? AND id <= ?'
++ 'SELECT COALESCE(SUM(delta), 0)::bigint AS bal FROM coin_transactions WHERE user_id = ? AND id <= ?'
).get(tx.user_id, tx.id);
return row.bal;
}
ddiff --git a/server/developer/usage.js b/server/developer/usage.js
iindex 48c49aeb..d6ab7578 100644
---- a/server/developer/usage.js
++++ b/server/developer/usage.js
@@@ -183,7 +183,7 @@ async function summary(db, projectId, { days = 30, env = 'all', now = () => Date
// Quotas (dev_quotas, staff-set): what their current window used, every environment together.
const seen = new Set((await db.prepare('SELECT DISTINCT capability, unit FROM dev_usage_daily WHERE project_id = ?').all(projectId)).map((r) => `${r.capability}|${r.unit}`));
-- const usedSince = db.prepare('SELECT COALESCE(SUM(quantity), 0) AS n FROM dev_usage_daily WHERE project_id = ? AND capability = ? AND unit = ? AND day >= ?');
++ const usedSince = db.prepare('SELECT COALESCE(SUM(quantity), 0)::bigint AS n FROM dev_usage_daily WHERE project_id = ? AND capability = ? AND unit = ? AND day >= ?');
const quotas = (await Promise.all((await db.prepare('SELECT * FROM dev_quotas WHERE project_id = ? ORDER BY capability').all(projectId)).map(async (qr) => {
const cap = capabilities.get(qr.capability);
const out = { capability: qr.capability, limit: qr.limit_value, window: qr.quota_window, unit: qr.unit, enforced_by: cap ? `openvibe.${cap.owner}` : null, used: null, remaining: null, window_start: null, note: null };
ddiff --git a/server/frame/service.js b/server/frame/service.js
iindex a39e2f1d..95ef83c4 100644
---- a/server/frame/service.js
++++ b/server/frame/service.js
@@@ -86,7 +86,7 @@ async function createFrameService(db, config, analytics, { privateKey = null, is
// Everything else: the navbar's page-view beacon, summed per site over 7 days.
let tools = rank.tools || [];
try {
-- const rows = await db.prepare("SELECT host, SUM(hits) AS n FROM frame_hits WHERE day >= substring(datetime('now', '-7 days'), 1, 10) GROUP BY host").all();
++ const rows = await db.prepare("SELECT host, SUM(hits)::bigint AS n FROM frame_hits WHERE day >= substring(datetime('now', '-7 days'), 1, 10) GROUP BY host").all();
const perSite = {}; const perTool = new Map();
const { catalog } = toolsCatalog.peek();
const toolOfHost = new Map();