← back to NationalPaperHangers

lib/utils.js

54 lines

// Tiny shared helpers used by both routes and scripts.
// Keep this file dependency-free.

function escapeHtml(s) {
  return String(s == null ? '' : s)
    .replace(/&/g, '&')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#039;');
}

// Returns a normalized http(s) URL string, or null if invalid.
function safeHttpUrl(input) {
  const v = String(input || '').trim();
  if (!v || v.length > 2000) return null;
  let u;
  try { u = new URL(v); } catch { return null; }
  if (u.protocol !== 'http:' && u.protocol !== 'https:') return null;
  return u.toString();
}

// Length-cap user input bound for varchar/text columns.
function clampLen(v, max) {
  const s = String(v == null ? '' : v);
  return s.length > max ? s.slice(0, max) : s;
}

// Parse a wall-clock time string ("H:MM", "HH:MM", or "HH:MM:SS") to its
// minute-of-day as a number, or null when malformed. Used to validate
// installer availability windows before they reach a `::time`-cast INSERT:
// a garbage value would 500 the query, and an inverted start/end pair would
// save cleanly but produce zero bookable slots (slots.js drops the invalid
// Interval), leaving the installer with no bookings and no error.
function clockMinutes(v) {
  const m = /^(\d{1,2}):([0-5]\d)(?::([0-5]\d))?$/.exec(String(v == null ? '' : v).trim());
  if (!m) return null;
  const h = Number(m[1]);
  if (h > 23) return null;
  return h * 60 + Number(m[2]) + (m[3] ? Number(m[3]) / 60 : 0);
}

// Single source of truth for "is this a usable email address". Mirrors the
// shape check the booking endpoint already uses, so signup, claim, and
// booking all reject the same garbage (e.g. "@", "a@", "a@b") instead of
// letting it into the installers.email / bookings.customer_email columns.
function isValidEmail(v) {
  const s = String(v == null ? '' : v).trim();
  if (!s || s.length > 254) return false;
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s);
}

module.exports = { escapeHtml, safeHttpUrl, clampLen, clockMinutes, isValidEmail };