63 lines · 2.8 KB
import { forbidden, notFoundError } from "../errors";
import { limitHeavy, limitWrite } from "../limits";
import { latestRuns } from "../mergefix/store";
import { startMergeFix } from "../mergefix/service";
import type { FixPlan } from "../mergefix/plan";
import { loadPull } from "../pulls";
import { hasPermission, loadRepoById } from "../repos";
import { scopedRepoPermission } from "../token-scope";
import { requireScopedRepo } from "./http";
import type { ApiContext } from "./http";

const HTTP_ACCEPTED = 202;

async function loadScopedPull(ctx: ApiContext, owner: string, name: string, number: number, needed: "read" | "write") {
  const { repo } = await requireScopedRepo(ctx, owner, name, "pulls", needed);
  const pull = await loadPull(repo.id, number);
  if (!pull) throw notFoundError("Pull request not found");
  return { repo, pull };
}

/** Starts an AI conflict fix; like update-branch, the token must also be able to push to the head repo. */
export async function startPullAiFix(ctx: ApiContext, owner: string, name: string, number: number) {
  await limitWrite(ctx.viewer);
  const { repo, pull } = await loadScopedPull(ctx, owner, name, number, "write");
  const headRepo = pull.head_repo_id === repo.id ? repo : await loadRepoById(pull.head_repo_id);
  if (!headRepo) throw notFoundError("Head repository was deleted");
  if (!hasPermission(await scopedRepoPermission(headRepo, ctx.credential, "contents"), "write")) {
    throw forbidden("This token cannot push to the head branch");
  }
  const id = await startMergeFix(pull, ctx.viewer, "button");
  return Response.json({ id }, { status: HTTP_ACCEPTED });
}

export async function getPullAiFix(ctx: ApiContext, owner: string, name: string, number: number) {
  await limitHeavy(ctx.viewer);
  const { pull } = await loadScopedPull(ctx, owner, name, number, "read");
  const [latest] = await latestRuns(pull.issue_id, 1);
  if (!latest) throw notFoundError("No AI fix has run for this pull request");
  const plan = latest.result_json ? (JSON.parse(latest.result_json) as FixPlan) : null;
  return Response.json({