mirror of
https://github.com/prdlk/leetcode.git
synced 2026-09-17 07:26:27 +00:00
93 lines
3.5 KiB
TypeScript
93 lines
3.5 KiB
TypeScript
/**
|
|||
|
|
* Nightly GitHub → D1 catalog reconcile. GitHub issues own the catalog
|
||
|
|
* (topics, problems, set/diff labels, milestones); this recomputes desired
|
||
|
|
* rows from scratch on every run — re-runs are no-ops. SRS-owned columns
|
||
|
|
* (stage, next_review) are NEVER overwritten; defer_until is set once, on
|
||
|
|
* first sight of a deferred problem. The Worker never invents catalog rows.
|
||
|
|
*/
|
||
|
|
import { addDays } from "./srs.ts";
|
||
|
|
import type { GitHub } from "./github.ts";
|
||
|
|
|
||
|
|
const TITLE_RE = /^LC (\d+) · (.+) · (Easy|Medium|Hard) · (core|optional|deferred)$/;
|
||
|
|
|
||
|
|
/** Deferred Hards enter the queue from this date, two per day. */
|
||
|
|
const DEFER_FROM = "2026-09-28";
|
||
|
|
|
||
|
|
export interface ReconcileReport {
|
||
|
|
topics: number;
|
||
|
|
problems: number;
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function reconcileCatalog(db: D1Database, gh: GitHub): Promise<ReconcileReport> {
|
||
|
|
interface TopicIssue {
|
||
|
|
number: number;
|
||
|
|
title: string;
|
||
|
|
milestone: number | null;
|
||
|
|
}
|
||
|
|
const topics: TopicIssue[] = [];
|
||
|
|
for await (const raw of gh.list(`/repos/${gh.repo}/issues?labels=topic&state=all`)) {
|
||
|
|
if (!raw || typeof raw !== "object") continue;
|
||
|
|
if ("pull_request" in raw) continue;
|
||
|
|
if (!("number" in raw) || typeof raw.number !== "number") continue;
|
||
|
|
if (!("title" in raw) || typeof raw.title !== "string") continue;
|
||
|
|
let milestone: number | null = null;
|
||
|
|
if (
|
||
|
|
"milestone" in raw &&
|
||
|
|
raw.milestone &&
|
||
|
|
typeof raw.milestone === "object" &&
|
||
|
|
"number" in raw.milestone &&
|
||
|
|
typeof raw.milestone.number === "number"
|
||
|
|
) {
|
||
|
|
milestone = raw.milestone.number;
|
||
|
|
}
|
||
|
|
topics.push({ number: raw.number, title: raw.title, milestone });
|
||
|
|
}
|
||
|
|
|
||
|
|
const statements: D1PreparedStatement[] = [];
|
||
|
|
for (const t of topics) {
|
||
|
|
statements.push(
|
||
|
|
db
|
||
|
|
.prepare(
|
||
|
|
`INSERT INTO topics (issue, name, milestone) VALUES (?1, ?2, ?3)
|
||
|
|
ON CONFLICT(issue) DO UPDATE SET name = ?2, milestone = ?3`,
|
||
|
|
)
|
||
|
|
.bind(t.number, t.title.replace(/^Topic \d+ — /, ""), t.milestone),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
let problems = 0;
|
||
|
|
let deferredSeen = 0;
|
||
|
|
for (const t of topics) {
|
||
|
|
for await (const raw of gh.list(`/repos/${gh.repo}/issues/${t.number}/sub_issues`)) {
|
||
|
|
if (!raw || typeof raw !== "object") continue;
|
||
|
|
if (!("number" in raw) || typeof raw.number !== "number") continue;
|
||
|
|
if (!("title" in raw) || typeof raw.title !== "string") continue;
|
||
|
|
const m = raw.title.match(TITLE_RE);
|
||
|
|
if (!m) continue; // non-curriculum sub-issue
|
||
|
|
const lc = Number(m[1]);
|
||
|
|
const set = m[4]!;
|
||
|
|
// Two deferred Hards per day from DEFER_FROM, in catalog walk order —
|
||
|
|
// applied only when the row is first created (SRS owns it afterwards).
|
||
|
|
const defer = set === "deferred" ? addDays(DEFER_FROM, Math.floor(deferredSeen / 2)) : null;
|
||
|
|
if (set === "deferred") deferredSeen++;
|
||
|
|
statements.push(
|
||
|
|
db
|
||
|
|
.prepare(
|
||
|
|
`INSERT INTO problems (lc_number, issue, topic_issue, title, difficulty, set_label, defer_until, next_review)
|
||
|
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?7)
|
||
|
|
ON CONFLICT(lc_number) DO UPDATE SET
|
||
|
|
issue = ?2, topic_issue = ?3, title = ?4, difficulty = ?5, set_label = ?6`,
|
||
|
|
)
|
||
|
|
.bind(lc, raw.number, t.number, m[2]!, m[3]!.toLowerCase(), set, defer),
|
||
|
|
);
|
||
|
|
problems++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// D1 batches are transactional; chunk to stay under statement limits.
|
||
|
|
for (let i = 0; i < statements.length; i += 50) {
|
||
|
|
await db.batch(statements.slice(i, i + 50));
|
||
|
|
}
|
||
|
|
return { topics: topics.length, problems };
|
||
|
|
}
|