/** * Generate a URL-friendly slug from a collection name */ export function generateSlug(name) { return name .toLowerCase() .trim() // Replace spaces and special characters with hyphens .replace(/[^a-z0-9]+/g, '-') // Remove leading/trailing hyphens .replace(/^-+|-+$/g, '') // Limit length to 50 characters .substring(0, 50) // Remove trailing hyphen if truncation created one .replace(/-+$/, ''); } /** * Generate a unique slug by checking against existing slugs */ export async function generateUniqueSlug(name, existingSlugs = []) { const baseSlug = generateSlug(name); // If base slug is unique, use it if (!existingSlugs.includes(baseSlug)) { return baseSlug; } // Find the next available number suffix let counter = 2; let uniqueSlug = `${baseSlug}-${counter}`; while (existingSlugs.includes(uniqueSlug)) { counter++; uniqueSlug = `${baseSlug}-${counter}`; } return uniqueSlug; } /** * Validate a slug format */ export function isValidSlug(slug) { if (!slug || typeof slug !== 'string') { return false; } // Must be 1-50 characters, lowercase letters, numbers, and hyphens only // Cannot start or end with hyphen const slugRegex = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/; return slugRegex.test(slug) && slug.length <= 50; } /** * Convert existing collection names to suggested slugs for migration */ export function suggestSlugForCollection(collection) { const baseSlug = generateSlug(collection.name); return { id: collection.id, name: collection.name, currentSlug: collection.slug || null, suggestedSlug: baseSlug }; }