fix(cli): move LeetCode fetches to GraphQL and restore history from the profile

LeetCode's REST /api/* endpoints now answer a Cloudflare 403 to non-browser
clients, which broke the picker's problem index on an empty cache. The index
and descriptions now come from /graphql, and test/submit fold the signed-in
profile's submission history into the solutions table once a day, so a lost
db or a fresh clone recovers both orderings.
This commit is contained in:
root committed 2026-11-02 12:56:51 -05:00
1 parent 83cb284e87
commit 7249da8bd6
3 files changed
+259 -45

No files matched your search

+35 -3
View File
@@ -9,7 +9,9 @@
*
* Every run backfills the db's solutions table with created_at (from the
* file's birthtime, mtime when the filesystem has none) so ordering works for
* files that predate the db. Files git reports as modified, added or
* files that predate the db, and at most once a day folds in the signed-in
* LeetCode profile's submission history (see db.ts), which survives a lost db
* or a fresh clone. Files git reports as modified, added or
* untracked come first, newest edit first — the problem being worked on is
* the focused option, so Enter runs it without a search. The rest are ordered
* per command — test lists the most recently tested first (never-tested
@@ -19,10 +21,10 @@
* Library: side-effect-free on import — nothing scans the filesystem until
* runOnSolution() is called.
*/
import { autocomplete, cancel, isCancel, log } from "@clack/prompts";
import { autocomplete, cancel, isCancel, log, spinner } from "@clack/prompts";
import { statSync } from "node:fs";
import { basename, join, relative } from "node:path";
import { problemDb } from "./db";
import { fetchSubmissions, leetcodeCredentials, problemDb } from "./db";
import { ROOT, WORK as WORK_DIR, parseWorkPath } from "./work";
/** Leading LC number of a work/ file, or undefined for unnumbered names. */
@@ -77,6 +79,36 @@ export async function runOnSolution(
new Date(stat.birthtimeMs || stat.mtimeMs).toISOString(),
);
}
// the profile's history: first and latest submission per work/ problem
if (db.historyStale()) {
const auth = leetcodeCredentials();
if (!auth) {
log.warn("Not signed in to leetcode-cli — run `leetcode login` to restore history from your profile.");
} else {
const s = spinner();
s.start("Syncing submission history from your LeetCode profile");
try {
const bySlug = new Map<string, number>();
for (const f of scanned) {
const parsed = parseWorkPath(f);
if (parsed) bySlug.set(parsed.slug, parsed.lc);
}
const history = new Map<number, { first: string; last: string }>();
for (const { slug, at } of await fetchSubmissions(auth)) {
const id = bySlug.get(slug);
if (id === undefined) continue;
const seen = history.get(id);
if (!seen) history.set(id, { first: at, last: at });
else if (at < seen.first) seen.first = at;
else if (at > seen.last) seen.last = at;
}
s.stop(`Synced submission history for ${db.foldHistory(history)} solutions`);
} catch (err) {
s.error(`Submission history sync failed (${err}) — using cached timestamps`);
}
}
}
const activity = db.solutionActivity();
// test → most recently tested first; submit → most recently created first