/** * PDF URL helpers for the Explorer (RHL-023). * * Why this file exists: * - The PDF endpoint is a binary stream and must NOT be called via apiClient (JSON-centric). * - We want centralized, testable URL construction for: * GET /api/files/:branch/:year/:month/:day/:filename * * Design goals: * - Pure functions (no React, no window, no Next runtime). * - Defensive encoding for filename (spaces, #, unicode, etc.). * - Minimal, predictable behavior. */ /** * Ensure a required string input is present and not empty/whitespace. * * Note: * - We validate emptiness using trim(), but we may return the original value * to avoid mutating semantics (important for filenames). * * @param {string} name * @param {unknown} value * @returns {string} */ function requireNonEmptyString(name, value) { if (typeof value !== "string") { throw new Error(`Route segment "${name}" must be a string`); } if (!value.trim()) { throw new Error(`Route segment "${name}" must not be empty`); } return value; } /** * Encode a standard route segment (branch/year/month/day). * - We normalize by trimming. * * @param {string} name * @param {unknown} value * @returns {string} */ function encodeSegment(name, value) { return encodeURIComponent(requireNonEmptyString(name, value).trim()); } /** * Encode a filename segment. * - We do NOT trim the filename (filenames are filesystem-exact). * - We still validate that it isn't empty/whitespace. * * @param {unknown} value * @returns {string} */ function encodeFilename(value) { return encodeURIComponent(requireNonEmptyString("filename", value)); } /** * Build the PDF stream URL (inline by default). * * @param {{ * branch: string, * year: string, * month: string, * day: string, * filename: string * }} input * @returns {string} */ export function buildPdfUrl({ branch, year, month, day, filename }) { const b = encodeSegment("branch", branch); const y = encodeSegment("year", year); const m = encodeSegment("month", month); const d = encodeSegment("day", day); const f = encodeFilename(filename); return `/api/files/${b}/${y}/${m}/${d}/${f}`; } /** * Build the PDF download URL (forces Content-Disposition: attachment). * * @param {{ * branch: string, * year: string, * month: string, * day: string, * filename: string * }} input * @returns {string} */ export function buildPdfDownloadUrl(input) { return `${buildPdfUrl(input)}?download=1`; }