117 lines · 4.7 KB
import { createServerFn } from "@tanstack/react-start";
import { isTakenDown } from "~/lib/account-disable";
import { z } from "zod";
import { viewerMiddleware } from "./middleware";
import { first } from "~/server/db";
import { repoDb } from "~/server/scoped-db";
import { limitBrowse } from "~/server/limits";
import { loadRepo, repoPermission } from "~/server/repos";
import { withStore } from "~/server/git/store";
import { accountName } from "~/lib/validation";

const ISSUE_REF = /^([^/]+)\/([^#]+)#(\d+)$/;
const COMMIT_REF = /^([^/]+)\/([^@]+)@([0-9a-f]{7,40})$/;
const FULL_SHA = 40;
const SHORT_SHA = 7;
const MAX_TEXT = 200;

async function readableRepo(owner: string, name: string, viewer: Parameters<typeof repoPermission>[1]) {
  const repo = await loadRepo(owner, name);
  if (!repo || (await repoPermission(repo, viewer)) === "none") return null;
  return repo;
}

/** Preview data for `data-hovercard` links rendered by the markdown pipeline. */
export const getHovercard = createServerFn({ method: "GET" })
  .middleware([viewerMiddleware])
  .validator(z.object({ kind: z.enum(["issue", "user", "team", "commit"]), ref: z.string().min(1).max(300) }))
  .handler(async ({ data, context }) => {
    await limitBrowse(context.viewer);
    if (data.kind === "user" || data.kind === "team") {
      const name = accountName.safeParse(data.ref.split("/")[0]);
      if (!name.success) return null;
      const account = await first<{
        id: string;
        name: string;
        display_name: string | null;
        bio: string | null;
        kind: string;
        website: string | null;
        avatar_key: string | null;