232 lines · 8.5 KB
import { z } from "zod";
import { all, batch, first, placeholders, run, stmt } from "./db";
import { randomId } from "./crypto";
import { badRequest, conflict, forbidden, notFoundError } from "./errors";
import { emitNotification, emitWebhook } from "./events/emit";
import { kickBranch, kickMergeAutomation } from "./merge-automation";
import { pullUrl } from "./pull-access";
import { addIssueEvent } from "./pulls";
import type { PullRow } from "./pulls";
import { rerequestReview } from "./review-requests";
import { markdownBody } from "~/lib/validation";
import type { Permission, RepoRow, Viewer } from "~/lib/types";

/** A resolved pull plus the caller's effective permission on its repo (already capped by any token scope). */
export type PullAccess = { repo: RepoRow; permission: Permission; pull: PullRow };

export const reviewCommentInput = z
  .object({
    path: z.string().min(1),
    line: z.number().int().positive(),
    side: z.enum(["LEFT", "RIGHT"]),
    startLine: z.number().int().positive().nullish(),
    startSide: z.enum(["LEFT", "RIGHT"]).nullish(),
    body: markdownBody.min(1),
  })
  .refine((comment) => !comment.startLine || comment.startLine < comment.line, "Start line must precede the end line");

export type ReviewCommentInput = z.infer<typeof reviewCommentInput>;
export type ReviewEvent = "approve" | "request_changes" | "comment";

async function insertReviewComments(
  pull: PullRow,
  viewer: Viewer,
  reviewId: string | null,
  comments: ReviewCommentInput[],
  inReplyTo: string | null,
) {
  const now = Date.now();
  await batch(
    comments.map((comment) =>