67 lines · 2.4 KB
import { all, first, run } from "./db";
import { HttpError } from "./errors";
import { randomId } from "./crypto";
import { assertBelowCap, CAPS } from "./limits";

const HTTP_UNPROCESSABLE = 422;

export type LabelRow = { id: string; name: string; color: string; description: string | null };
export type LabelInput = { name: string; color: string; description: string | null };

export async function listRepoLabels(repoId: string): Promise<Array<LabelRow & { open_count: number }>> {
  return all<LabelRow & { open_count: number }>(
    `SELECT l.id, l.name, l.color, l.description,
       (SELECT COUNT(*) FROM issue_labels il JOIN issues i ON i.id = il.issue_id WHERE il.label_id = l.id AND i.state = 'open') AS open_count
     FROM labels l WHERE l.repo_id = ? ORDER BY l.name`,
    repoId,
  );
}

export async function loadLabel(repoId: string, id: string): Promise<LabelRow | null> {
  return first<LabelRow>("SELECT id, name, color, description FROM labels WHERE id = ? AND repo_id = ?", id, repoId);
}

/** Creates (id null) or updates a label; returns its id. Callers must have checked write access. */
export async function saveLabel(repoId: string, id: string | null, input: LabelInput): Promise<string> {
  const clash = await first<{ id: string }>(
    "SELECT id FROM labels WHERE repo_id = ? AND name = ? COLLATE NOCASE AND id != ?",
    repoId,
    input.name,
    id ?? "",
  );
  if (clash) throw new HttpError(HTTP_UNPROCESSABLE, "A label with that name already exists");
  if (!id)
    assertBelowCap(
      (await first<{ count: number }>("SELECT COUNT(*) AS count FROM labels WHERE repo_id = ?", repoId))?.count ?? 0,
      CAPS.labelsPerRepo,
      "labels",
    );
  if (id) {
    await run(