feat: add "Reset for Smoke Test" button in admin settings

Truncates sources, curriculum, content, quiz banks/results/cache, topics
and relations in dependency order so AI-generated state can be wiped
between smoke runs without leaving dangling references. Handbook sync
state is cleared by default (otherwise re-sync is a no-op); user
progress and leaderboard are opt-in. Team members, settings, and LLM
telemetry are preserved.

UI lives in Admin → Settings → Danger Zone and requires typing RESET
before the button enables. Per-collection deletion counts are reported.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
RaymondVerhoef
2026-05-22 16:42:54 +02:00
parent 25cae2fea9
commit 3626cc0525
2 changed files with 159 additions and 1 deletions

View File

@@ -338,6 +338,56 @@ export async function getRecentLlmCalls(limit = 100) {
}
}
// ── Reset for Smoke Testing ──────────────────────────────────────────────────
/**
* Truncate the collections that hold AI-generated and progress data.
*
* Order matters: rows that reference `topics.id` (relations, content,
* quiz_banks) are deleted before topics themselves so we never leave
* dangling references mid-wipe.
*
* `team_members`, `settings`, and `llm_calls` are intentionally preserved.
*
* @param {{ includeHandbookState?: boolean, includeProgress?: boolean }} [opts]
* @returns {Promise<Array<{collection: string, deleted: number, error?: string}>>}
*/
export async function resetForSmokeTest({
includeHandbookState = true,
includeProgress = false,
} = {}) {
const collections = [
'relations',
'content',
'quiz_banks',
'quiz_results',
'quiz_cache',
'curriculum',
'sources',
'topics',
];
if (includeHandbookState) collections.push('handbook_sync_state');
if (includeProgress) collections.push('learn_progress', 'leaderboard');
const results = [];
for (const name of collections) {
try {
const rows = await pb.collection(name).getFullList({ fields: 'id', requestKey: null });
// Sequential deletes — bulk delete-many isn't in the JS SDK and parallel
// deletes on the same collection sometimes 409 in PocketBase.
let deleted = 0;
for (const r of rows) {
await pb.collection(name).delete(r.id, { requestKey: null });
deleted++;
}
results.push({ collection: name, deleted });
} catch (err) {
results.push({ collection: name, deleted: 0, error: err?.message || String(err) });
}
}
return results;
}
// ── Handbook Sync State ───────────────────────────────────────────────────────
export async function getHandbookSyncStates() {