54 lines · 2.6 KB
import { createServerFn } from "@tanstack/react-start";
import { currentSessionHandle } from "~/server/auth";
import { z } from "zod";
import { requireViewer, viewerMiddleware } from "./middleware";
import { limitBrowse, limitWrite } from "~/server/limits";
import { requireRepoAccess } from "~/server/repos";
import { listRefs, resolveRevision, splitRevisionPath } from "~/server/git/refs";
import { assertVisibleRevision } from "~/server/git/reachable";
import { notFoundError } from "~/server/errors";
import { editorSessionInfo, mintEditorToken, resolveEditorToken } from "~/server/editor/session";
import { editorOriginFor } from "~/server/editor/origin";
import { getRequestUrl } from "@tanstack/react-start/server";
import { repoRef } from "~/lib/validation";

/** `/edit/<ref>` with no file path opens the workspace editor; `/edit/<ref>/<path>` keeps the single-file editor. */
export const resolveEditTarget = createServerFn({ method: "GET" })
  .middleware([viewerMiddleware])
  .validator(repoRef.extend({ splat: z.string().default("") }))
  .handler(async ({ data, context }) => {
    await limitBrowse(context.viewer);
    const { repo } = await requireRepoAccess(data.owner, data.repo, context.viewer, "read");
    const refs = await listRefs(repo);
    const splat = data.splat.replace(/\/+$/, "") || repo.default_branch;
    const resolved = splitRevisionPath(refs, splat);
    if (!resolved) throw notFoundError("Branch or path not found");
    if (resolved.path) return { kind: "file" as const };
    return {
      kind: "workspace" as const,
      ref: resolved.revision.name,
      refKind: resolved.revision.kind,
      editorOrigin: editorOriginFor(new URL(getRequestUrl())),
    };
  });

/** Mints the short-lived, ref-scoped token the sandboxed editor frame uses instead of the session cookie. */
export const mintEditorSession = createServerFn({ method: "POST" })
  .middleware([viewerMiddleware])
  .validator(repoRef.extend({ ref: z.string().min(1).max(250) }))
  .handler(async ({ data, context }) => {
    const viewer = requireViewer(context.viewer);