47 lines · 1.8 KB
import { env } from "cloudflare:workers";
import { first } from "./db";
import { branchSha, listRefs } from "./git/refs";
import { withStore } from "./git/store";
import { renderMarkdown } from "./markdown";
import { REPO_SELECT } from "./repos";
import type { RepoRow } from "~/lib/types";

const README_NAMES = new Set(["readme.md", "readme.markdown"]);
const MAX_README_BYTES = 256 * 1024;
const README_CACHE_TTL_SECONDS = 60 * 60 * 24 * 7;
const decoder = new TextDecoder();

/** Renders README.md from the public `<name>/<name>` repo, cached in KV by commit. */
export async function profileReadme(accountName: string): Promise<string | null> {
  const repo = await first<RepoRow>(
    `${REPO_SELECT} WHERE a.name = ?1 AND r.name = ?1 AND r.visibility = 'public' AND r.status = 'ready'`,
    accountName,
  );
  if (!repo) return null;
  const refs = await listRefs(repo);
  const sha = branchSha(refs, repo.default_branch);
  if (!sha) return null;
  const cacheKey = `profile-readme:${repo.id}:${sha}`;
  const cached = await env.KV.get(cacheKey);
  if (cached !== null) return cached || null;
  const html = await withStore(repo.artifacts_name, async (store) => {
    const commit = await store.commit(sha);
    if (!commit) return "";
    const entries = await store.listTree(commit.treeSha, "");
    const entry = entries.find(
      (candidate) => candidate.type !== "tree" && README_NAMES.has(candidate.name.toLowerCase()),
    );
    if (!entry) return "";
    const bytes = await store.blob(entry.sha);
    if (!bytes || bytes.byteLength > MAX_README_BYTES) return "";
    return renderMarkdown(decoder.decode(bytes), {
      owner: repo.owner_name,
      repo: repo.name,
      ref: repo.default_branch,