Analyse: alle Beendigungswege schalten den Messdienst ab (Kunde/Projekt weg, Domain, Löschfrist, Widerruf Wechsel)
Messdienst: POST /a/admin/sites/:key/off setzt active=0 und offSince einmalig; Portal-Client stopSite. analyse.js: endAnalyse als gemeinsamer Kern, endAnalyseForCustomer, analyseOnProjectChanged, analyseDisabled (Sperre/Löschfrist) und täglicher Abgleich runAnalyseReconcile (Projekt/Kunde weg, Domains nachziehen, K18-Wiederholung, Messdienst für off/disabled, fehlende Entfernen-Tickets nachholen). Entfernen-Ticket je Abschaltung gekennzeichnet (kein Doppelticket, auch wenn das Speichern der ID scheiterte). Hooks: Kunde löschen, updateWebsiteProject, withdrawOrder (Wechsel), siteFeatures.disable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fbc40252a3
commit
78170567b4
17 files changed
+616
-70
No files matched your search
@@ -110,6 +110,18 @@ function createHandlerServer({ config, db, role, geo, salt, now, onSitesChanged
|
||||
return send(res, 200, { ok: true });
|
||||
}
|
||||
|
||||
// Beenden ohne Domainliste: active=0 und offSince einmalig (Portal: stopSite)
|
||||
if (role === 'admin' && method === 'POST' && parts[0] === 'a' && parts[1] === 'admin' && parts[2] === 'sites' && parts.length === 5 && parts[4] === 'off') {
|
||||
if (!tokenOk(req.headers.authorization, config.adminToken)) return send(res, 401, { error: 'Nicht autorisiert' });
|
||||
const key = parts[3];
|
||||
if (!SITE_KEY.test(key)) return send(res, 400, { error: 'Ungültiger Website-Schlüssel' });
|
||||
const old = db.prepare('SELECT offSince FROM sites WHERE siteKey = ?').get(key);
|
||||
if (!old) return send(res, 404, { error: 'Website nicht gefunden' });
|
||||
db.prepare('UPDATE sites SET active = 0, offSince = ? WHERE siteKey = ?').run(old.offSince || new Date().toISOString(), key);
|
||||
onSitesChanged?.();
|
||||
return send(res, 200, { ok: true });
|
||||
}
|
||||
|
||||
if (role === 'admin' && method === 'GET' && parts[0] === 'a' && parts[1] === 'admin' && parts[2] === 'stats' && parts.length === 4) {
|
||||
if (!tokenOk(req.headers.authorization, config.adminToken)) return send(res, 401, { error: 'Nicht autorisiert' });
|
||||
const key = parts[3];
|
||||
|
||||
@@ -166,3 +166,44 @@ test('Datei-DB: zweimal öffnen (idempotent) und WAL', () => {
|
||||
assert.equal(d2.prepare('PRAGMA journal_mode').get().journal_mode, 'wal');
|
||||
d2.close();
|
||||
});
|
||||
|
||||
test('POST …/off: 401 ohne Token, 404 unbekannt, setzt active=0 und offSince einmalig', async () => {
|
||||
const t = await setup();
|
||||
assert.equal((await t.call('POST', `/a/admin/sites/${KEY}/off`, undefined, null)).status, 401);
|
||||
assert.equal((await t.call('POST', `/a/admin/sites/${KEY}/off`, undefined, 'falsch')).status, 401);
|
||||
assert.equal((await t.call('POST', `/a/admin/sites/${KEY}/off`)).status, 404);
|
||||
assert.equal((await t.call('POST', '/a/admin/sites/kurz/off')).status, 400);
|
||||
await t.call('PUT', `/a/admin/sites/${KEY}`, valid);
|
||||
assert.equal(row(t.db).active, 1);
|
||||
const r = await t.call('POST', `/a/admin/sites/${KEY}/off`);
|
||||
assert.equal(r.status, 200);
|
||||
const s1 = row(t.db);
|
||||
assert.equal(s1.active, 0);
|
||||
assert.match(s1.offSince, /^\d{4}-\d\d-\d\dT/);
|
||||
assert.deepEqual(JSON.parse(s1.domains), ['abc.project.webklar.com']);
|
||||
t.db.prepare('UPDATE sites SET offSince = ? WHERE siteKey = ?').run('2026-01-01T00:00:00.000Z', KEY);
|
||||
assert.equal((await t.call('POST', `/a/admin/sites/${KEY}/off`)).status, 200);
|
||||
assert.equal(row(t.db).offSince, '2026-01-01T00:00:00.000Z');
|
||||
t.close();
|
||||
});
|
||||
|
||||
test('POST …/off: ruft onSitesChanged, danach verwirft /a/e Ereignisse und w.js ist leer', async () => {
|
||||
const db = openDb(':memory:');
|
||||
const pub = createPublicServer({ config: {}, db });
|
||||
const adm = createAdminServer({ config: { adminToken: 't' }, db, onSitesChanged: () => pub.invalidateSites() });
|
||||
await Promise.all([pub, adm].map((x) => new Promise((r) => x.listen(0, '127.0.0.1', r))));
|
||||
const pb = `http://127.0.0.1:${pub.address().port}`;
|
||||
const ab = `http://127.0.0.1:${adm.address().port}`;
|
||||
await fetch(`${ab}/a/admin/sites/${KEY}`, { method: 'PUT', headers: { Authorization: 'Bearer t', 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ projectId: 'p', domains: ['abc.project.webklar.com'], active: true }) });
|
||||
const ev = () => fetch(`${pb}/a/e`, { method: 'POST', headers: { 'Content-Type': 'text/plain', Origin: 'https://abc.project.webklar.com', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0) Chrome/120' },
|
||||
body: JSON.stringify({ t: 'pv', site: KEY, path: '/', vp: 'd' }) });
|
||||
await ev();
|
||||
const before = db.prepare('SELECT COUNT(*) c FROM events_raw').get().c;
|
||||
assert.equal(before, 1);
|
||||
assert.equal((await fetch(`${ab}/a/admin/sites/${KEY}/off`, { method: 'POST', headers: { Authorization: 'Bearer t' } })).status, 200);
|
||||
await ev();
|
||||
assert.equal(db.prepare('SELECT COUNT(*) c FROM events_raw').get().c, before);
|
||||
assert.equal((await (await fetch(`${pb}/a/w.js?s=${KEY}`)).text()).length < 200, true);
|
||||
pub.close(); adm.close();
|
||||
});
|
||||
Reference in new issue
Block a user