const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]{1,62}[a-z0-9]$|^[a-z0-9]{3}$/ function simpleHash(str) { let h = 0 for (let i = 0; i < str.length; i++) { h = ((h << 5) - h) + str.charCodeAt(i) h |= 0 } return Math.abs(h).toString(36) } function slugify(title) { let base = String(title || '').trim().toLowerCase() .replace(/\s+/g, '-') .replace(/[^a-z0-9-]/g, '') .replace(/-+/g, '-') .replace(/^-|-$/g, '') if (base.length < 3) { base = 'article-' + simpleHash(String(title || Date.now())) } return base.slice(0, 58) } function isValidSlug(slug) { return SLUG_PATTERN.test(String(slug || '')) } async function ensureUniqueSlug(db, slug, excludeId = null) { let candidate = slug let n = 2 while (true) { const sql = excludeId ? 'SELECT id FROM article WHERE slug = ? AND id != ? LIMIT 1' : 'SELECT id FROM article WHERE slug = ? LIMIT 1' const params = excludeId ? [candidate, excludeId] : [candidate] const rows = await db.query(sql, params) if (!rows || rows.length === 0) return candidate const suffix = `-${n}` candidate = `${slug.slice(0, 64 - suffix.length)}${suffix}` n++ } } module.exports = { slugify, isValidSlug, ensureUniqueSlug, SLUG_PATTERN }