import type { Context, Hono } from "hono"; import { MAX_CHARS } from "../issues/spam.ts"; import { openMirror } from "../mirror/store.ts"; import { html } from "../pages/html.ts"; import { plural } from "../pages/layout.ts"; import { type OperatorRelease, operatorReleases, setRelease } from "../releases/releases.ts"; import { type AdminContext, adminPage, formField, notice, textFields } from "./console.ts"; import { allRepos, findRepo } from "./store.ts"; /** * ยง24's Releases section: versions with download counts, displayed notes and hiding. Everything * lives in kepr.db; nothing is deleted, since the bucket key is read-only. */ export function registerReleases(app: Hono, ctx: AdminContext): void { app.get("/admin/releases", (c) => c.html(releasesPage(c, ctx, ""))); app.post("/admin/releases/:repo/:version", async (c) => { const repo = findRepo(ctx.kepr, c.req.param("repo")); const release = repo && operatorReleases(ctx.kepr, openMirror(ctx.mirrorDir, repo.name), repo.name).find( (r) => r.version === c.req.param("version"), ); if (!repo || !release) return c.notFound(); const fields = await textFields(c); const notes = (fields.notes ?? "").replace(/\r\n/g, "\n"); if (notes.length > MAX_CHARS) { const error = `${repo.name} ${release.version}: notes are over ${MAX_CHARS.toLocaleString("en-US")} characters.`; return c.html(releasesPage(c, ctx, error), 400); } // Notes equal to the record's are no override, so a later record edit would show through. const override = notes === release.recordNotes ? null : notes; setRelease(ctx.kepr, repo.name, release.version, fields.hidden === "1", override); return c.redirect(`/admin/releases#${repo.name}-${release.version}`, 303); }); } function releasesPage(c: Context, ctx: AdminContext, error: string): string { const sections = allRepos(ctx.kepr).flatMap((repo) => { const releases = operatorReleases(ctx.kepr, openMirror(ctx.mirrorDir, repo.name), repo.name); if (releases.length === 0) return []; return html`

${repo.name}

${releases.map((release) => releaseForm(c, repo.name, release))}
`; }); const body = html`${notice(error)} ${ sections.length > 0 ? sections : html`

no releases in the bucket yet

` }`; return adminPage(c, ctx, "releases", body); } function releaseForm(c: Context, repo: string, release: OperatorRelease) { const total = release.downloads.reduce((sum, [, n]) => sum + n, 0); const id = `${repo}-${release.version}`; return html`

${/* this row stays one line: splitting its spans changes the HTML that tests/console.test.ts pins */ ""}${html`${release.version}`}${html`${release.hidden ? "hidden" : "shown"}`}${html`${plural(total, "download")}`}${release.downloads.map( ([platform, n]) => html`${platform} ${n}`, )}

${formField(c)}
`; }