105 lines · 4.6 KB
import { z } from "zod";
import { forbidden, notFoundError } from "../errors";
import { limitHeavy, limitWrite } from "../limits";
import { latestRuns } from "../mergefix/store";
import { startMergeFix } from "../mergefix/service";
import { applyProposal } from "../mergefix/apply";
import { MERGEFIX_MODES } from "../mergefix/proposal";
import type { FixPlan } from "../mergefix/plan";
import type { PullRow } from "../pulls";
import type { RepoRow } from "~/lib/types";
import { loadPull } from "../pulls";
import { assertNotArchived, hasPermission, loadRepoById } from "../repos";
import { scopedRepoPermission } from "../token-scope";
import { readJson, requireScopedRepo } from "./http";
import type { ApiContext } from "./http";

const HTTP_ACCEPTED = 202;
const MAX_APPLY_FILES = 100;

const startBody = z.object({ mode: z.enum(MERGEFIX_MODES).optional() });
const applyBody = z.object({
  files: z.array(z.string().min(1).max(4096)).min(1).max(MAX_APPLY_FILES).optional(),
  confirm_low_confidence: z.boolean().optional(),
});

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 };
}

async function requireHeadToken(ctx: ApiContext, repo: RepoRow, pull: PullRow): Promise<void> {
  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");
  }
}