143 lines
5.2 KiB
JavaScript
143 lines
5.2 KiB
JavaScript
#!/usr/bin/env node
|
||
/**
|
||
* Aggregiert falukant_log.moneyflow nach activity (reale Buchungen aus dem Spiel).
|
||
*
|
||
* cd backend && npm run diag:moneyflow
|
||
*
|
||
* Umgebung:
|
||
* DIAG_DAYS=30 — Fenster in Tagen (1–3650, Standard 30)
|
||
* DIAG_USER_ID=123 — optional: nur dieser falukant_user
|
||
* DIAG_QUERY_TIMEOUT_MS, DIAG_AUTH_TIMEOUT_MS — wie diag:town-worth
|
||
*/
|
||
import { readFileSync } from 'node:fs';
|
||
import { fileURLToPath } from 'node:url';
|
||
import { dirname, join } from 'node:path';
|
||
|
||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||
const sqlByActivity = join(__dirname, '../sql/diagnostics/falukant_moneyflow_by_activity.sql');
|
||
const sqlTotals = join(__dirname, '../sql/diagnostics/falukant_moneyflow_window_totals.sql');
|
||
|
||
const QUERY_TIMEOUT_MS = Number.parseInt(process.env.DIAG_QUERY_TIMEOUT_MS || '60000', 10);
|
||
const AUTH_TIMEOUT_MS = Number.parseInt(process.env.DIAG_AUTH_TIMEOUT_MS || '25000', 10);
|
||
|
||
process.env.QUIET_ENV_LOGS = process.env.QUIET_ENV_LOGS || '1';
|
||
process.env.DOTENV_CONFIG_QUIET = process.env.DOTENV_CONFIG_QUIET || '1';
|
||
if (!process.env.DB_CONNECT_TIMEOUT_MS) {
|
||
process.env.DB_CONNECT_TIMEOUT_MS = '15000';
|
||
}
|
||
|
||
await import('../config/loadEnv.js');
|
||
const { sequelize } = await import('../utils/sequelize.js');
|
||
|
||
function withTimeout(promise, ms, onTimeoutError) {
|
||
let timerId;
|
||
const timeoutPromise = new Promise((_, reject) => {
|
||
timerId = setTimeout(() => reject(new Error(onTimeoutError)), ms);
|
||
});
|
||
return Promise.race([promise, timeoutPromise]).finally(() => {
|
||
clearTimeout(timerId);
|
||
});
|
||
}
|
||
|
||
function raceQuery(sql) {
|
||
return withTimeout(
|
||
sequelize.query(sql),
|
||
QUERY_TIMEOUT_MS,
|
||
`Abfrage-Timeout nach ${QUERY_TIMEOUT_MS} ms (DIAG_QUERY_TIMEOUT_MS)`
|
||
);
|
||
}
|
||
|
||
function raceAuth() {
|
||
return withTimeout(
|
||
sequelize.authenticate(),
|
||
AUTH_TIMEOUT_MS,
|
||
`authenticate() Timeout nach ${AUTH_TIMEOUT_MS} ms (DIAG_AUTH_TIMEOUT_MS).`
|
||
);
|
||
}
|
||
|
||
function printConnectionHints() {
|
||
const port = process.env.DB_PORT || '5432';
|
||
const host = process.env.DB_HOST || '?';
|
||
const local = host === '127.0.0.1' || host === 'localhost' || host === '::1';
|
||
console.error('');
|
||
console.error('[diag] Mögliche Ursachen:');
|
||
if (local) {
|
||
console.error(' • SSH-Tunnel: Läuft z. B. ssh -L ' + port + ':127.0.0.1:5432 …? Dann DB_HOST=127.0.0.1 DB_PORT=' + port + ' (DB_SSL meist aus).');
|
||
console.error(' • Falscher lokaler Forward-Port in .env (DB_PORT).');
|
||
} else {
|
||
console.error(' • PostgreSQL erwartet TLS: in .env DB_SSL=1 setzen (ggf. DB_SSL_REJECT_UNAUTHORIZED=0 bei selbstsigniert).');
|
||
console.error(' • Falscher Port: DB_PORT=' + port + ' prüfen.');
|
||
console.error(' • Server-Firewall: deine IP muss für Port', port, 'auf', host, 'freigeschaltet sein.');
|
||
}
|
||
console.error(' • Test: nc -zv', host, port);
|
||
console.error('');
|
||
}
|
||
|
||
function parseDays() {
|
||
const raw = process.env.DIAG_DAYS;
|
||
const n = raw === undefined || raw === '' ? 30 : Number.parseInt(raw, 10);
|
||
if (!Number.isFinite(n) || n < 1 || n > 3650) {
|
||
throw new Error('DIAG_DAYS muss eine Ganzzahl zwischen 1 und 3650 sein');
|
||
}
|
||
return n;
|
||
}
|
||
|
||
function parseOptionalUserFilter() {
|
||
const raw = process.env.DIAG_USER_ID;
|
||
if (raw === undefined || raw === '') {
|
||
return '';
|
||
}
|
||
const uid = Number.parseInt(raw, 10);
|
||
if (!Number.isInteger(uid) || uid < 1) {
|
||
throw new Error('DIAG_USER_ID muss eine positive Ganzzahl sein (falukant_user.id)');
|
||
}
|
||
return ` AND m.falukant_user_id = ${uid}`;
|
||
}
|
||
|
||
function applyPlaceholders(sql, days, userFilter) {
|
||
return sql
|
||
.replace(/__DIAG_DAYS__/g, String(days))
|
||
.replace(/__DIAG_USER_FILTER__/g, userFilter);
|
||
}
|
||
|
||
try {
|
||
const days = parseDays();
|
||
const userFilter = parseOptionalUserFilter();
|
||
const host = process.env.DB_HOST || '(unbekannt)';
|
||
const t0 = Date.now();
|
||
|
||
console.log('');
|
||
console.log('[diag] moneyflow — Fenster:', days, 'Tage', userFilter ? `(User ${process.env.DIAG_USER_ID})` : '(alle Nutzer)');
|
||
console.log('[diag] PostgreSQL: authenticate() … (Host:', host + ', Port:', process.env.DB_PORT || '5432', ', DB_SSL:', process.env.DB_SSL === '1' ? '1' : '0', ')');
|
||
console.log('');
|
||
|
||
await raceAuth();
|
||
console.log('[diag] authenticate() ok nach', Date.now() - t0, 'ms');
|
||
|
||
await sequelize.query("SET statement_timeout = '60s'");
|
||
|
||
const sql1 = applyPlaceholders(readFileSync(sqlByActivity, 'utf8'), days, userFilter);
|
||
const sql2 = applyPlaceholders(readFileSync(sqlTotals, 'utf8'), days, userFilter);
|
||
|
||
const t1 = Date.now();
|
||
const [totalsRows] = await raceQuery(sql2);
|
||
console.log('[diag] Fenster-Totals nach', Date.now() - t1, 'ms');
|
||
console.table(totalsRows);
|
||
|
||
const t2 = Date.now();
|
||
const [byActivity] = await raceQuery(sql1);
|
||
console.log('[diag] Nach activity nach', Date.now() - t2, 'ms (gesamt', Date.now() - t0, 'ms)');
|
||
console.log('');
|
||
console.table(byActivity);
|
||
|
||
await sequelize.close();
|
||
process.exit(0);
|
||
} catch (err) {
|
||
console.error(err.message || err);
|
||
printConnectionHints();
|
||
try {
|
||
await sequelize.close();
|
||
} catch (_) {}
|
||
process.exit(1);
|
||
}
|