Export: Formelschutz nur fuer Text (inkl. Tab/CR), CSV mit Rechten 0600, Runbook

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-01 18:50:36 +00:00
1 parent 495c3a2c40
commit 546d3c84d0
2 files changed
+28 -3

No files matched your search

+7 -2
View File
@@ -1,5 +1,10 @@
#!/usr/bin/env node
// CSV-Export einer Website (Tageswerte, Einzelereignisse, Klicks). Öffnet die Datenbank nur lesend.
//
// Ablauf (Runbook): Im Container webklar-analytics ausführen (Code liegt dort unter /analytics, schreibbar ist nur /srv/webklar-analytics):
// docker exec webklar-analytics node /analytics/scripts/export-site.mjs <siteKey> --out /srv/webklar-analytics/exports/<siteKey>
// Die CSV-Dateien enthalten Besucher-Hashes (Rechte 0600) und müssen dem Kunden als Kopie innerhalb von zwei Wochen
// übergeben werden (AVV Nr. 9); danach den Export-Ordner wieder löschen.
import { DatabaseSync } from 'node:sqlite';
import { mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
@@ -11,7 +16,7 @@ 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}`;
if (typeof v === 'string' && /^[=+\-@\t\r]/.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';
@@ -31,7 +36,7 @@ export function exportSite({ dbPath, siteKey, outDir }) {
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');
for (const [n, c] of Object.entries(files)) writeFileSync(join(outDir, n), c, { encoding: 'utf8', mode: 0o600 });
return Object.keys(files).map((n) => join(outDir, n));
} finally { db.close(); }
}