| 1 | /** |
| 2 | * A workflow's status badge: an SVG saying how its newest finished run went, |
| 3 | * on `?branch=` (the default branch, else any) and `?event=` when given. |
| 4 | * Anyone may load a public repository's; a private repository's needs |
| 5 | * someone who can see it, and is never cached where others could read it. |
| 6 | */ |
| 7 | import type { Route } from "./+types/actions-badge"; |
| 8 | import { badgeState, badgeSvg } from "../../lib/badge"; |
| 9 | import { actions, repos } from "../../lib/services.server"; |
| 10 | import { getViewer } from "../../lib/session.server"; |
| 11 | |
| 12 | function svg(body: string, isPrivate: boolean, status = 200): Response { |
| 13 | return new Response(body, { |
| 14 | status, |
| 15 | headers: { |
| 16 | "content-type": "image/svg+xml; charset=utf-8", |
| 17 | // Shown in READMEs elsewhere, which fetch it through their own caches. |
| 18 | "cache-control": isPrivate ? "private, no-store" : "public, max-age=60, s-maxage=60", |
| 19 | "x-content-type-options": "nosniff", |
| 20 | "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'", |
| 21 | }, |
| 22 | }); |
| 23 | } |
| 24 | |
| 25 | export async function loader({ params, context, request }: Route.LoaderArgs) { |
| 26 | const viewer = getViewer(context); |
| 27 | const path = { namespace: params.owner, name: params.repo }; |
| 28 | const query = new URL(request.url).searchParams; |
| 29 | const branch = query.get("branch")?.trim() || undefined; |
| 30 | const event = query.get("event")?.trim() || undefined; |
| 31 | const repo = await repos.get(path, viewer).catch(() => null); |
| 32 | // Not there, or not this viewer's to see: the same answer either way. |
| 33 | if (!repo?.ok) return new Response("Not found.\n", { status: 404, headers: { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" } }); |
| 34 | const isPrivate = repo.value.isPrivate; |
| 35 | const workflows = await actions.workflows(path, viewer); |
| 36 | const file = params.file; |
| 37 | const workflow = workflows.ok ? workflows.value.find((w) => w.path.endsWith(`/${file}`) || w.id === file) : undefined; |
| 38 | if (!workflow) return svg(badgeSvg(file, "no status"), isPrivate, 404); |
| 39 | const runsOn = async (onBranch: string | undefined) => { |
| 40 | const runs = await actions.runs(path, viewer, { workflow: workflow.id, branch: onBranch, event, limit: 20 }); |
| 41 | return runs.ok ? runs.value : []; |
| 42 | }; |
| 43 | // The default branch unless one is asked for; any branch when it has none. |
| 44 | let runs = await runsOn(branch ?? repo.value.defaultBranch); |
| 45 | if (!branch && !runs.some((run) => run.status === "completed")) runs = await runsOn(undefined); |
| 46 | return svg(badgeSvg(workflow.name, badgeState(runs)), isPrivate); |
| 47 | } |