patx/youtube-clone

/** Small request/response helpers shared by the router. */

export class HttpError extends Error {
  constructor(status, message) {
    super(message);
    this.status = status;
  }
}

export const bad = (msg) => { throw new HttpError(400, msg); };
export const unauthorized = (msg = 'You need to sign in to do that.') => { throw new HttpError(401, msg); };
export const forbidden = (msg = 'You are not allowed to do that.') => { throw new HttpError(403, msg); };
export const notFound = (msg = 'Not found.') => { throw new HttpError(404, msg); };

export function sendJson(res, status, payload, headers = {}) {
  const body = Buffer.from(JSON.stringify(payload));
  res.writeHead(status, {
    'Content-Type': 'application/json; charset=utf-8',
    'Content-Length': body.length,
    'Cache-Control': 'no-store',
    ...headers,
  });
  res.end(body);
}

export async function readBody(req, limit = 1024 * 1024) {
  const chunks = [];
  let size = 0;
  for await (const chunk of req) {
    size += chunk.length;
    if (size > limit) throw new HttpError(413, 'Request body is too large.');
    chunks.push(chunk);
  }
  return Buffer.concat(chunks);
}

export async function readJson(req) {
  const raw = await readBody(req);
  if (!raw.length) return {};
  try {
    const parsed = JSON.parse(raw.toString('utf8'));
    if (parsed === null || typeof parsed !== 'object') bad('Expected a JSON object.');
    return parsed;
  } catch (err) {
    if (err instanceof HttpError) throw err;
    throw new HttpError(400, 'Request body was not valid JSON.');
  }
}

/** Trim + require a string field, with a max length. */
export function field(obj, name, { max = 5000, min = 1, label = name } = {}) {
  const value = typeof obj[name] === 'string' ? obj[name].trim() : '';
  if (value.length < min) bad(`${label} is required.`);
  if (value.length > max) bad(`${label} must be ${max} characters or fewer.`);
  return value;
}

export function intParam(value, fallback, { min = 0, max = Number.MAX_SAFE_INTEGER } = {}) {
  const n = Number.parseInt(value, 10);
  if (!Number.isFinite(n)) return fallback;
  return Math.min(max, Math.max(min, n));
}