import { httpError } from '../../shared/http/httpError.mjs'; export function normalizeArticle(input) { if (!input || typeof input !== 'object' || Array.isArray(input)) { throw httpError(400, 'Article payload must be an object'); } const article = { ...input, slug: readRequiredString(input, 'slug'), title: readRequiredString(input, 'title'), date: readRequiredString(input, 'date'), author: readRequiredString(input, 'author'), categories: normalizeCategories(input.categories), cover: readRequiredString(input, 'cover'), excerpt: readRequiredString(input, 'excerpt'), contentHtml: readRequiredString(input, 'contentHtml', { preserveWhitespace: true }), readingMinutes: normalizeReadingMinutes(input.readingMinutes), }; if (/[/?#]/.test(article.slug)) { throw httpError(400, 'Slug must not contain "/", "?" or "#"'); } if (Number.isNaN(new Date(article.date).getTime())) { throw httpError(400, 'Date must be a valid date string'); } return article; } export function assertUniqueSlug(articles, slug, exceptSlug) { const duplicate = articles.find((article) => article.slug === slug && article.slug !== exceptSlug); if (duplicate) { throw httpError(409, `Slug "${slug}" is already used`); } } function readRequiredString(input, key, options = {}) { const value = input[key]; if (typeof value !== 'string') { throw httpError(400, `"${key}" must be a string`); } const normalized = options.preserveWhitespace ? value : value.trim(); if (!normalized) { throw httpError(400, `"${key}" is required`); } return normalized; } function normalizeCategories(value) { const categories = Array.isArray(value) ? value : String(value || '') .split(',') .map((category) => category.trim()); const normalized = categories.filter(Boolean); if (normalized.length === 0) { throw httpError(400, 'At least one category is required'); } return [...new Set(normalized)]; } function normalizeReadingMinutes(value) { const minutes = Number(value); if (!Number.isInteger(minutes) || minutes < 1) { throw httpError(400, '"readingMinutes" must be a positive integer'); } return minutes; }