mirror of
https://github.com/prdlk/leetcode.git
synced 2026-09-17 15:36:26 +00:00
95 lines
3.5 KiB
TypeScript
95 lines
3.5 KiB
TypeScript
#!/usr/bin/env bun
|
|||
|
|
/**
|
||
|
|
* One-shot migration: .github/srs/srs.json → D1.
|
||
|
|
*
|
||
|
|
* Run AFTER the catalog reconcile has filled `problems` (the import only
|
||
|
|
* overlays SRS-owned fields — stage, next_review, defer_until — and inserts
|
||
|
|
* attempts/topic counters). Re-runnable without duplicates: import-sourced
|
||
|
|
* attempts are wiped and re-inserted, everything else upserts.
|
||
|
|
*
|
||
|
|
* bun apps/api/scripts/import-srs.ts # local D1 (wrangler dev state)
|
||
|
|
* bun apps/api/scripts/import-srs.ts --remote # production D1
|
||
|
|
*
|
||
|
|
* Prints row counts; verify them against srs.json before deleting anything.
|
||
|
|
*/
|
||
|
|
import { $ } from "bun";
|
||
|
|
import { join } from "node:path";
|
||
|
|
|
||
|
|
const ROOT = join(import.meta.dir, "..", "..");
|
||
|
|
const API = join(ROOT, "api");
|
||
|
|
const REMOTE = process.argv.includes("--remote");
|
||
|
|
|
||
|
|
interface Attempt {
|
||
|
|
date: string;
|
||
|
|
kind: string;
|
||
|
|
result: string;
|
||
|
|
}
|
||
|
|
interface Problem {
|
||
|
|
issue: number;
|
||
|
|
topic: number;
|
||
|
|
difficulty: string;
|
||
|
|
set: string;
|
||
|
|
solved_on?: string;
|
||
|
|
stage: string;
|
||
|
|
next_review?: string;
|
||
|
|
defer_until?: string;
|
||
|
|
history: Attempt[];
|
||
|
|
}
|
||
|
|
interface State {
|
||
|
|
problems: Record<string, Problem>;
|
||
|
|
topics: Record<string, { misses: number; boost: boolean }>;
|
||
|
|
drill_pool_used: number[];
|
||
|
|
}
|
||
|
|
|
||
|
|
const state: State = JSON.parse(
|
||
|
|
await Bun.file(join(ROOT, ".github", "srs", "srs.json")).text(),
|
||
|
|
);
|
||
|
|
|
||
|
|
const q = (v: string | null | undefined) => (v == null ? "NULL" : `'${v}'`);
|
||
|
|
const lines: string[] = ["DELETE FROM attempts WHERE source = 'import';"];
|
||
|
|
|
||
|
|
let attempts = 0;
|
||
|
|
for (const [lc, p] of Object.entries(state.problems)) {
|
||
|
|
lines.push(
|
||
|
|
`UPDATE problems SET stage = ${q(p.stage)}, next_review = ${q(p.next_review ?? null)}, ` +
|
||
|
|
`defer_until = ${q(p.defer_until ?? null)} WHERE lc_number = ${Number(lc)};`,
|
||
|
|
);
|
||
|
|
for (const a of p.history) {
|
||
|
|
lines.push(
|
||
|
|
`INSERT INTO attempts (lc_number, date, kind, result, source) ` +
|
||
|
|
`VALUES (${Number(lc)}, ${q(a.date)}, ${q(a.kind)}, ${q(a.result)}, 'import');`,
|
||
|
|
);
|
||
|
|
attempts++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
for (const [topic, t] of Object.entries(state.topics)) {
|
||
|
|
lines.push(
|
||
|
|
`UPDATE topics SET misses = ${t.misses}, boost = ${t.boost ? 1 : 0} WHERE issue = ${Number(topic)};`,
|
||
|
|
);
|
||
|
|
}
|
||
|
|
for (const lc of state.drill_pool_used) {
|
||
|
|
lines.push(`INSERT OR IGNORE INTO drill_pool_used (lc_number) VALUES (${lc});`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const sqlPath = join(API, "migrations", ".import.sql");
|
||
|
|
await Bun.write(sqlPath, lines.join("\n") + "\n");
|
||
|
|
const flag = REMOTE ? "--remote" : "--local";
|
||
|
|
await $`bunx wrangler d1 execute srs ${flag} --file ${sqlPath}`.cwd(API);
|
||
|
|
await $`rm ${sqlPath}`;
|
||
|
|
|
||
|
|
const counts =
|
||
|
|
await $`bunx wrangler d1 execute srs ${flag} --json --command ${"SELECT (SELECT COUNT(*) FROM problems) AS problems, (SELECT COUNT(*) FROM problems WHERE stage != 'new') AS laddered, (SELECT COUNT(*) FROM problems WHERE defer_until IS NOT NULL) AS deferred, (SELECT COUNT(*) FROM attempts WHERE source='import') AS imported_attempts, (SELECT COUNT(*) FROM topics) AS topics, (SELECT COUNT(*) FROM drill_pool_used) AS drills_used"}`
|
||
|
|
.cwd(API)
|
||
|
|
.json();
|
||
|
|
|
||
|
|
const expected = {
|
||
|
|
json_problems: Object.keys(state.problems).length,
|
||
|
|
json_attempts: attempts,
|
||
|
|
json_laddered: Object.values(state.problems).filter((p) => p.stage !== "new").length,
|
||
|
|
json_deferred: Object.values(state.problems).filter((p) => p.defer_until).length,
|
||
|
|
json_topics: Object.keys(state.topics).length,
|
||
|
|
json_drills_used: state.drill_pool_used.length,
|
||
|
|
};
|
||
|
|
console.log("expected from srs.json:", JSON.stringify(expected));
|
||
|
|
console.log("in D1:", JSON.stringify(counts[0]?.results?.[0] ?? counts));
|