syntaxai/tdd.md · main · src / d21_handlers_commit_view.ts
// c21 — handler: SAMA-native commit view at
// GET /GIT/:repo/commit/:sha
// and a raw-diff sibling at
// GET /GIT/:repo/commit/:sha.diff
//
// Composes c14 (Forgejo HTTP), c31 (diff parser), c51 (render). The
// route prefix is uppercase /GIT/ to make it visually distinct from
// the markdown content sections (/sama, /blog, /guides). Owner is
// implicit (single-tenant — LIVE_REPO_OWNER) and never appears in
// the URL surface.
import { renderNotFound, htmlResponse } from "./b51_render_layout.ts";
import { getCommit, getCommitDiff } from "./c14_git.ts";
import { LIVE_REPO_OWNER, LIVE_REPO_NAME } from "./a31_site_config.ts";
import { parseUnifiedDiff } from "./a31_diff_parse.ts";
import { renderCommitView } from "./b51_render_commit.ts";
// Repo + sha shape — paranoid because these go straight into a
// Forgejo URL. Repo allows letters/digits/hyphens/underscores/dots;
// sha is hex 7-64 (Forgejo accepts shortened SHAs but our render assumes
// full ones because we use them in URLs).
const SAFE_OWNER_REPO = /^[A-Za-z0-9][A-Za-z0-9._-]{0,99}$/;
const SAFE_SHA = /^[a-f0-9]{7,64}$/;
const isValid = (repo: string, sha: string): boolean =>
SAFE_OWNER_REPO.test(repo) && SAFE_SHA.test(sha);
export const commitViewHandler = async (
req: Request & { params: { repo: string; sha: string } },
): Promise<Response> => {
const { repo } = req.params;
// The :sha param may carry a trailing ".diff" because the route
// pattern doesn't have a separate one. Normalise + branch.
const rawSha = req.params.sha;
const wantsDiff = rawSha.endsWith(".diff");
const sha = wantsDiff ? rawSha.slice(0, -5) : rawSha;
const fullPath = `/GIT/${repo}/commit/${rawSha}`;
if (!isValid(repo, sha)) {
const html = await renderNotFound(fullPath);
return htmlResponse(html, 404);
}
// /GIT/ now serves only the local bare repo (LIVE_REPO_NAME via
// c14_git). Other repos would historically have been proxied to
// Forgejo for agent katas — that's a separate concern and
// currently 404s.
if (repo !== LIVE_REPO_NAME) {
const html = await renderNotFound(fullPath);
return htmlResponse(html, 404);
}
if (wantsDiff) {
const diffText = await getCommitDiff(sha);
if (diffText === null) {
const html = await renderNotFound(fullPath);
return htmlResponse(html, 404);
}
return new Response(diffText, {
headers: {
"Content-Type": "text/plain; charset=utf-8",
"Cache-Control": "public, max-age=300",
},
});
}
const commit = await getCommit(sha);
if (commit === null) {
const html = await renderNotFound(fullPath);
return htmlResponse(html, 404);
}
const diffText = (await getCommitDiff(sha)) ?? "";
const diff = parseUnifiedDiff(diffText);
// c14_git's GitCommit shape matches what c51_render_commit needs
// (it used to take ForgejoCommitDetail; same field names + types).
const detail = {
sha: commit.sha,
parents: commit.parents,
authorName: commit.authorName,
authorEmail: commit.authorEmail,
authorDate: commit.authorDate,
committerName: commit.committerName,
committerEmail: commit.committerEmail,
committerDate: commit.committerDate,
message: commit.message,
};
const html = await renderCommitView({
owner: LIVE_REPO_OWNER,
repo,
detail,
diff,
});
return htmlResponse(html);
};