feat(cli): add solution activity tracking and ordering for test/submit commands

This commit is contained in:
Prad Nukala
2026-08-26 15:50:58 -04:00
parent 1b5bdc4ceb
commit f086c2b39a
2 changed files with 110 additions and 6 deletions
+51
View File
@@ -10,6 +10,12 @@
* are ever fetched, and paid-only problems are skipped because GraphQL
* returns null content for them.
*
* A `solutions` table tracks the lifecycle of each work/ file by LC number:
* `created_at` (backfilled from the file's birthtime on every picker run),
* `last_tested` and `last_submitted` (stamped on exit 0 of leetcode-cli).
* The test picker sorts by last_tested, the submit picker by created_at, and
* submit hides anything with a last_submitted.
*
* Library: side-effect-free on import — nothing is opened or fetched until
* problemDb() is called. Index upserts never touch the description column, so
* a daily refresh cannot evict cached statements.
@@ -27,6 +33,13 @@ export interface Problem {
paidOnly: boolean;
}
/** Lifecycle of one work/ solution, keyed by LC number. ISO timestamps. */
export interface SolutionActivity {
createdAt: string;
lastTested: string | null;
lastSubmitted: string | null;
}
const DIFFICULTY = ["", "Easy", "Medium", "Hard"] as const;
const INDEX_TTL_MS = 24 * 60 * 60 * 1000;
@@ -109,6 +122,13 @@ export function problemDb(path = DB_PATH) {
paid_only INTEGER NOT NULL,
description TEXT
);
CREATE TABLE IF NOT EXISTS solutions (
id INTEGER PRIMARY KEY,
created_at TEXT NOT NULL,
last_tested TEXT,
last_submitted TEXT
);
DROP TABLE IF EXISTS submissions;
CREATE TABLE IF NOT EXISTS meta (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
@@ -177,6 +197,37 @@ export function problemDb(path = DB_PATH) {
db.query("UPDATE problems SET description = ?2 WHERE slug = ?1").run(slug, html);
},
/** Per-solution lifecycle rows keyed by LC number. */
solutionActivity(): Map<number, SolutionActivity> {
const rows = db
.query<{ id: number; created_at: string; last_tested: string | null; last_submitted: string | null }, []>(
"SELECT id, created_at, last_tested, last_submitted FROM solutions",
)
.all();
return new Map(
rows.map((r) => [r.id, { createdAt: r.created_at, lastTested: r.last_tested, lastSubmitted: r.last_submitted }]),
);
},
/** Register a solution's creation time; existing rows are left alone. */
ensureSolution(id: number, createdAt: string): void {
db.query("INSERT INTO solutions (id, created_at) VALUES (?1, ?2) ON CONFLICT(id) DO NOTHING").run(id, createdAt);
},
markTested(id: number): void {
db.query(
`INSERT INTO solutions (id, created_at, last_tested) VALUES (?1, ?2, ?2)
ON CONFLICT(id) DO UPDATE SET last_tested = excluded.last_tested`,
).run(id, new Date().toISOString());
},
markSubmitted(id: number): void {
db.query(
`INSERT INTO solutions (id, created_at, last_submitted) VALUES (?1, ?2, ?2)
ON CONFLICT(id) DO UPDATE SET last_submitted = excluded.last_submitted`,
).run(id, new Date().toISOString());
},
close(): void {
db.close();
},