mise ~/.config/mise/config.toml tools: crush@0.91.0

feat(api): add /admin/solved endpoint and commit source handling
This commit is contained in:
Prad Nukala committed 2026-08-25 15:43:21 -04:00
1 parent aba3d9b18b
commit e4de74c935
4 files changed
+99 -12

No files matched your search

+59 -4
View File
@@ -22,6 +22,7 @@ import {
daysBetween,
etDate,
etHour,
getProblem,
logAttempt,
weekdayOf,
} from "./srs.ts";
@@ -29,6 +30,13 @@ import { buildStats } from "./stats.ts";
// ── shared side effects after a state write ──────────────────────
/** Project-field half of a mirror; shared by every write path. */
async function mirrorFields(mirror: Mirror, outcome: LogOutcome): Promise<void> {
await mirror.setStage(outcome.issue, outcome.stage);
await mirror.setTargetDate(outcome.issue, outcome.next_review);
if (outcome.first) await mirror.setFirstAttempt(outcome.issue, outcome.result);
}
/**
* Mirror one logged attempt into GitHub: Project fields always; on a
* first-ever pass also close the problem's sub-issue (comment first — repo
@@ -37,10 +45,7 @@ import { buildStats } from "./stats.ts";
async function mirrorOutcome(env: Env, outcome: LogOutcome, source: string): Promise<void> {
if (outcome.error || outcome.duplicate) return;
const gh = github(env.GH_PAT, env.REPO);
const mirror: Mirror = projectMirror(gh, env.REPO.split("/")[0]!);
await mirror.setStage(outcome.issue, outcome.stage);
await mirror.setTargetDate(outcome.issue, outcome.next_review);
if (outcome.first) await mirror.setFirstAttempt(outcome.issue, outcome.result);
await mirrorFields(projectMirror(gh, env.REPO.split("/")[0]!), outcome);
if (outcome.first && outcome.result === "pass") {
try {
await gh.closeIssue(
@@ -200,6 +205,55 @@ async function adminAuthorized(request: Request, env: Env): Promise<boolean> {
return timingSafeEqual(header.slice(7), env.LINK_KEY);
}
/**
* 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.
*
* 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.
*/
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[] = [];
for (const item of raw) {
const lc: unknown = item;
if (typeof lc !== "number" || !Number.isFinite(lc)) {
return new Response(`not an LC number: ${JSON.stringify(lc)}`, { status: 400 });
}
lcs.push(lc);
}
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) {
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 +2`);
continue;
}
const outcome = await logAttempt(env.DB, { lc, date, result: "pass", source: "commit" });
if (outcome.error) skipped.push(outcome.error);
else if (outcome.duplicate) skipped.push(`LC ${lc}: already at stage ${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]!);
await mirrorFields(mirror, outcome);
}
}
return Response.json({ date, logged, skipped, warnings: mirror?.warnings ?? [] });
}
async function handleAdmin(request: Request, env: Env, path: string): Promise<Response> {
if (!(await adminAuthorized(request, env))) return new Response("unauthorized", { status: 401 });
const url = new URL(request.url);
@@ -209,6 +263,7 @@ async function handleAdmin(request: Request, env: Env, path: string): Promise<Re
if (path === "/admin/reconcile") {
return Response.json(await reconcileCatalog(env.DB, gh()));
}
if (path === "/admin/solved") return await handleSolved(request, env, date);
if (path === "/admin/digest") {
const report = await sendDigest(env, date, {
dry: url.searchParams.get("dry") === "1",