mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-03 05:27:36 +00:00
feat(api): add dueToday endpoint, shared DUE_WHERE constant, and commit rung handling
This commit is contained in:
1 parent
38fbb18f32
commit
5ea123e46d
3 files changed
+124
-33
No files matched your search
+35
-14
@@ -19,7 +19,9 @@ import { type Mirror, projectMirror } from "./mirror.ts";
|
||||
import {
|
||||
CAMPAIGN_START,
|
||||
type LogOutcome,
|
||||
type Stage,
|
||||
daysBetween,
|
||||
dueToday,
|
||||
etDate,
|
||||
etHour,
|
||||
getProblem,
|
||||
@@ -202,43 +204,56 @@ async function adminAuthorized(request: Request, env: Env): Promise<boolean> {
|
||||
/**
|
||||
* Solutions landing in `work/` are the third way a problem gets solved, after
|
||||
* the digest tap and a /done comment. `close-solved` posts its whole
|
||||
* implemented set here on every push to main: logAttempt ignores anything
|
||||
* already on the ladder, so this is a total recompute like the reconcilers
|
||||
* that call it — a re-run changes nothing.
|
||||
* implemented set here on every push to main, each entry tagged with the
|
||||
* BUCKET the file sits in — 1 for the first solve, 3 and 7 for the blind
|
||||
* re-solves the picker scaffolds. The bucket names the ladder rung the file
|
||||
* settles, and logAttempt writes only when the problem is standing on that
|
||||
* rung, so this is a total recompute like the reconcilers that call it: a
|
||||
* re-run changes nothing, and a pushed re-solve advances +3 → +7 instead of
|
||||
* being invisible.
|
||||
*
|
||||
* Closing the sub-issue stays with close-solved: it is the side that knows
|
||||
* which files at which commit, so this path mirrors Project fields only.
|
||||
* Charts read D1 live, so a push moves them within the 5-minute cache.
|
||||
*/
|
||||
const RUNG_OF_BUCKET: Record<number, Stage> = { 1: "new", 3: "+3", 7: "+7" };
|
||||
|
||||
async function handleSolved(request: Request, env: Env, date: string): Promise<Response> {
|
||||
const body: unknown = await request.json().catch(() => null);
|
||||
const raw: unknown = body && typeof body === "object" && "lc" in body ? body.lc : null;
|
||||
if (!Array.isArray(raw)) return new Response('expected {"lc": [<number>, ...]}', { status: 400 });
|
||||
const lcs: number[] = [];
|
||||
const raw: unknown = body && typeof body === "object" && "solved" in body ? body.solved : null;
|
||||
const shape = 'expected {"solved": [{"lc": <number>, "bucket": 1|3|7}, ...]}';
|
||||
if (!Array.isArray(raw)) return new Response(shape, { status: 400 });
|
||||
const entries: { lc: number; rung: Stage; bucket: number }[] = [];
|
||||
for (const item of raw) {
|
||||
const lc: unknown = item;
|
||||
if (!item || typeof item !== "object") return new Response(shape, { status: 400 });
|
||||
const lc: unknown = "lc" in item ? item.lc : null;
|
||||
const bucket: unknown = "bucket" in item ? item.bucket : null;
|
||||
if (typeof lc !== "number" || !Number.isFinite(lc)) {
|
||||
return new Response(`not an LC number: ${JSON.stringify(lc)}`, { status: 400 });
|
||||
}
|
||||
lcs.push(lc);
|
||||
const rung = typeof bucket === "number" ? RUNG_OF_BUCKET[bucket] : undefined;
|
||||
if (rung === undefined) return new Response(`not a work/ bucket: ${JSON.stringify(bucket)}`, { status: 400 });
|
||||
entries.push({ lc, rung, bucket });
|
||||
}
|
||||
const dry = new URL(request.url).searchParams.get("dry") === "1";
|
||||
|
||||
const logged: string[] = [];
|
||||
const skipped: string[] = [];
|
||||
let mirror: Mirror | undefined;
|
||||
for (const lc of lcs) {
|
||||
for (const { lc, rung, bucket } of entries) {
|
||||
if (dry) {
|
||||
const p = await getProblem(env.DB, lc);
|
||||
if (!p) skipped.push(`LC ${lc} is not in the curriculum`);
|
||||
else if (p.stage !== "new") skipped.push(`LC ${lc}: already at stage ${p.stage}`);
|
||||
else logged.push(`LC ${lc}: would enter the ladder at +3`);
|
||||
else if (p.stage !== rung) skipped.push(`LC ${lc}: work/${bucket} settles ${rung}, stage is ${p.stage}`);
|
||||
else if (rung === "new") logged.push(`LC ${lc}: would enter the ladder at +3`);
|
||||
else logged.push(`LC ${lc}: would pass ${rung} and move on`);
|
||||
continue;
|
||||
}
|
||||
const outcome = await logAttempt(env.DB, { lc, date, result: "pass", source: "commit" });
|
||||
const outcome = await logAttempt(env.DB, { lc, date, result: "pass", source: "commit", rung });
|
||||
if (outcome.error) skipped.push(outcome.error);
|
||||
else if (outcome.duplicate) skipped.push(`LC ${lc}: already at stage ${outcome.stage}`);
|
||||
else {
|
||||
else if (outcome.duplicate) {
|
||||
skipped.push(`LC ${lc}: work/${bucket} settles ${rung}, stage is ${outcome.stage}`);
|
||||
} else {
|
||||
logged.push(outcomeLine(outcome));
|
||||
// One mirror for the batch: field IDs resolve once, not per problem.
|
||||
mirror ??= projectMirror(github(env.GH_PAT, env.REPO), env.REPO.split("/")[0]!);
|
||||
@@ -271,6 +286,12 @@ async function handleAdmin(request: Request, env: Env, path: string): Promise<Re
|
||||
if (path === "/admin/review") {
|
||||
return Response.json(await createReviewIssue(env, gh(), date));
|
||||
}
|
||||
// What is due on `date`, straight out of the ladder — the ONE answer to
|
||||
// "which problems are open today", so the daily review issue and the digest
|
||||
// cannot disagree. Read-only; the caller decides what to render.
|
||||
if (path === "/admin/due") {
|
||||
return Response.json(await dueToday(env.DB, date));
|
||||
}
|
||||
return new Response("not found", { status: 404 });
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user