117 lines · 4.4 KB
import { all, first, run } from "../db";
import { randomId } from "../crypto";
import { assertBelowCap } from "../limits";
import { HttpError, forbidden, notFoundError } from "../errors";

const MAX_LOCKS_PER_REPO = 1000;
const MAX_LOCK_PATH = 1024;
export const DEFAULT_LOCK_PAGE = 100;
export const MAX_LOCK_PAGE = 100;

export type LfsLock = { id: string; path: string; locked_at: string; owner: { name: string } };

type LockRow = { id: string; path: string; created_at: number; owner_id: string; owner_name: string };

const LOCK_SELECT = `SELECT l.id, l.path, l.created_at, l.owner_id, a.name AS owner_name
  FROM lfs_locks l JOIN accounts a ON a.id = l.owner_id`;

export function lockJson(row: LockRow): LfsLock {
  return {
    id: row.id,
    path: row.path,
    locked_at: new Date(row.created_at).toISOString(),
    owner: { name: row.owner_name },
  };
}

/** Cursor is `<created_at>:<id>` for stable keyset pagination. */
function parseCursor(cursor: string | undefined): [number, string] | null {
  if (!cursor) return null;
  const separator = cursor.indexOf(":");
  const createdAt = Number(cursor.slice(0, separator));
  return separator > 0 && Number.isFinite(createdAt) ? [createdAt, cursor.slice(separator + 1)] : null;
}

export function clampLimit(value: unknown): number {
  const parsed = Number(value);
  if (!Number.isInteger(parsed) || parsed <= 0) return DEFAULT_LOCK_PAGE;
  return Math.min(parsed, MAX_LOCK_PAGE);
}