| 1 | /** |
| 2 | * Some files of a change with their lines, fetched by the diff view as it |
| 3 | * reaches files a large pull request, commit or comparison was sent |
| 4 | * without (lib/diff.ts firstScreens, components/diff-view.tsx). |
| 5 | * |
| 6 | * `base` and `head` are the commits the page compared, so the answer never |
| 7 | * changes: it is kept in the data centre's cache once the viewer is known |
| 8 | * to read the repository, and the browser keeps it too. |
| 9 | */ |
| 10 | import { data } from "react-router"; |
| 11 | |
| 12 | import type { FileDiff } from "@g1t/contracts"; |
| 13 | |
| 14 | import type { Route } from "./+types/diff-files"; |
| 15 | import { immutable } from "../../lib/immutable.server"; |
| 16 | import { repos } from "../../lib/services.server"; |
| 17 | import { getViewer, unwrap } from "../../lib/session.server"; |
| 18 | |
| 19 | const COMMIT = /^[0-9a-f]{40}$/; |
| 20 | /** The most files one request reads; the repos service reads no more than 50. */ |
| 21 | export const MAX_PATHS = 20; |
| 22 | |
| 23 | export async function loader({ params, context, request }: Route.LoaderArgs) { |
| 24 | const url = new URL(request.url); |
| 25 | const head = url.searchParams.get("head") ?? ""; |
| 26 | const base = url.searchParams.get("base") ?? ""; |
| 27 | const paths = [...new Set(url.searchParams.getAll("path"))].sort(); |
| 28 | if (!COMMIT.test(head) || (base !== "" && !COMMIT.test(base)) || paths.length === 0 || paths.length > MAX_PATHS) { |
| 29 | throw data({ error: "Give head (and base) as full commit hashes and 1 to 20 paths." }, { status: 400 }); |
| 30 | } |
| 31 | const viewer = getViewer(context); |
| 32 | const repo = unwrap(await repos.get({ namespace: params.owner, name: params.repo }, viewer)); |
| 33 | // With no base the head is compared with its first parent, as the page |
| 34 | // that sent the file did: a commit's own page, or a change with no |
| 35 | // shared history. |
| 36 | const files = await immutable(`diff-files:${repo.id}:${base}:${head}:${paths.join("\n")}`, async () => { |
| 37 | const compared = await repos.compare(repo.id, viewer, base || null, head, null, paths); |
| 38 | return compared.ok ? compared.value.files : null; |
| 39 | }); |
| 40 | if (!files) throw data({ error: "Those files could not be compared." }, { status: 404 }); |
| 41 | return Response.json({ files } satisfies { files: FileDiff[] }, { |
| 42 | headers: { "cache-control": "private, max-age=31536000, immutable" }, |
| 43 | }); |
| 44 | } |