51 lines
2.7 KiB
JavaScript
51 lines
2.7 KiB
JavaScript
#!/usr/bin/env node
|
|
// CSV-Export einer Website (Tageswerte, Einzelereignisse, Klicks). Öffnet die Datenbank nur lesend.
|
|
import { DatabaseSync } from 'node:sqlite';
|
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
import { join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const RE_KEY = /^[a-z0-9]{16,40}$/;
|
|
const iso = (ts) => new Date(ts).toISOString();
|
|
|
|
function cell(v) {
|
|
if (v === null || v === undefined) return '';
|
|
let s = String(v);
|
|
if (/^[=+\-@]/.test(s)) s = `'${s}`;
|
|
return /[",\r\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
|
|
}
|
|
const csv = (head, rows) => [head, ...rows].map((r) => r.map(cell).join(',')).join('\r\n') + '\r\n';
|
|
|
|
export function exportSite({ dbPath, siteKey, outDir }) {
|
|
if (!RE_KEY.test(siteKey || '')) throw new Error('Ungültiger Website-Schlüssel');
|
|
const db = new DatabaseSync(dbPath, { readOnly: true });
|
|
try {
|
|
if (!db.prepare('SELECT 1 FROM sites WHERE siteKey = ?').get(siteKey)) throw new Error('Unbekannte Website');
|
|
const daily = db.prepare('SELECT day, dim, key, count FROM daily WHERE site = ? ORDER BY day, dim, key').all(siteKey);
|
|
const ev = db.prepare('SELECT ts, type, path, vp, ref, country, device, visitor, sec, scroll, kind, host FROM events_raw WHERE site = ? ORDER BY ts, id').all(siteKey);
|
|
const cl = db.prepare('SELECT ts, path, vp, x, y, sel FROM clicks WHERE site = ? ORDER BY ts, id').all(siteKey);
|
|
mkdirSync(outDir, { recursive: true });
|
|
const files = {
|
|
[`${siteKey}-tageswerte.csv`]: csv(['day', 'dim', 'key', 'count'], daily.map((r) => [r.day, r.dim, r.key, r.count])),
|
|
[`${siteKey}-ereignisse.csv`]: csv(['time', 'type', 'path', 'vp', 'ref', 'country', 'device', 'visitor', 'sec', 'scroll', 'kind', 'host'],
|
|
ev.map((r) => [iso(r.ts), r.type, r.path, r.vp, r.ref, r.country, r.device, r.visitor, r.sec, r.scroll, r.kind, r.host])),
|
|
[`${siteKey}-klicks.csv`]: csv(['time', 'path', 'vp', 'x', 'y', 'sel'], cl.map((r) => [iso(r.ts), r.path, r.vp, r.x, r.y, r.sel])),
|
|
};
|
|
for (const [n, c] of Object.entries(files)) writeFileSync(join(outDir, n), c, 'utf8');
|
|
return Object.keys(files).map((n) => join(outDir, n));
|
|
} finally { db.close(); }
|
|
}
|
|
|
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
const args = process.argv.slice(2);
|
|
const opt = (n, d) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] : d; };
|
|
const siteKey = args.find((a, i) => !a.startsWith('--') && !(i > 0 && args[i - 1].startsWith('--')));
|
|
try {
|
|
const files = exportSite({ dbPath: opt('--db', '/srv/webklar-analytics/analytics.db'), siteKey, outDir: opt('--out', '.') });
|
|
console.log(files.join('\n'));
|
|
} catch (e) {
|
|
console.error(`Export fehlgeschlagen: ${e.message}`);
|
|
process.exit(1);
|
|
}
|
|
}
|