import type { Context, Hono } from "hono"; import { syncAll } from "../bucket/sync.ts"; import { html } from "../pages/html.ts"; import { formatDate } from "../pages/layout.ts"; import { type AdminContext, adminPage, formField, notice, textFields } from "./console.ts"; import { type AdminRepo, allRepos, findRepo, type RepoEdit, updateRepo } from "./store.ts"; const MAX_FIELD = 500; const HTTP_URL = /^https?:\/\/[^\s]+$/i; const SORT_ORDER = /^-?\d{1,6}$/; /** §24's Repos section: visibility (§20), card settings, sync status and "sync now". */ export function registerRepos(app: Hono, ctx: AdminContext): void { app.get("/admin/repos", (c) => c.html(reposPage(c, ctx, ""))); app.post("/admin/sync", async (c) => { await syncAll(ctx.sync); return c.redirect("/admin/repos", 303); }); app.post("/admin/repos/:repo", async (c) => { const repo = findRepo(ctx.kepr, c.req.param("repo")); if (!repo) return c.notFound(); const fields = await textFields(c); const text = (name: string) => (fields[name] ?? "").trim(); const edit: RepoEdit = { public: fields.public === "1" ? 1 : 0, category: text("category"), homepage: text("homepage"), sort_order: Number(text("sort_order") || "0"), install1: text("install1"), install2: text("install2"), install3: text("install3"), }; const refuse = (error: string) => c.html(reposPage(c, ctx, `${repo.name}: ${error}`), 400); if (Object.values(fields).some((value) => value.length > MAX_FIELD)) { return refuse(`keep each field under ${MAX_FIELD} characters.`); } if (edit.homepage !== "" && !HTTP_URL.test(edit.homepage)) { return refuse("the homepage must be an http or https address."); } if (!SORT_ORDER.test(text("sort_order") || "0")) return refuse("the order must be a number."); if ([edit.install1, edit.install2, edit.install3].some((line) => /[\r\n]/.test(line))) { return refuse("an install line is one line."); } updateRepo(ctx.kepr, repo.name, edit); return c.redirect(`/admin/repos#repo-${repo.name}`, 303); }); } function reposPage(c: Context, ctx: AdminContext, error: string): string { const repos = allRepos(ctx.kepr); const body = html`${notice(error)}
${formField(c)}
${repos.length === 0 && html`

no repos found in the bucket yet

`} ${repos.map((repo) => repoForm(c, repo))}`; return adminPage(c, ctx, "repos", body); } function repoForm(c: Context, repo: AdminRepo) { const input = (name: keyof RepoEdit, label: string) => html` `; const synced = repo.last_sync_at === null ? "never synced" : `last sync ${formatDate(repo.last_sync_at)} UTC`; return html`

${repo.public ? html`${repo.name}` : repo.name} ${repo.public ? "public" : "private"}${synced}

${ repo.last_sync_error && html`

sync error: ${repo.last_sync_error}

` }
${formField(c)} ${input("category", "category")} ${input("homepage", "homepage")} ${input("sort_order", "card order")} ${input("install1", "install line 1")} ${input("install2", "install line 2")} ${input("install3", "install line 3")}
`; }