/** * Migration Script: Generate Slugs for Existing Listings * * This script generates SEO-friendly slugs for all existing businesses * and commercial properties that don't have slugs yet. * * Run with: npx ts-node scripts/migrate-slugs.ts */ import { Pool } from 'pg'; import { drizzle, NodePgDatabase } from 'drizzle-orm/node-postgres'; import { sql, eq, isNull } from 'drizzle-orm'; import * as schema from '../src/drizzle/schema'; // Slug generation function (copied from utils for standalone execution) function generateSlug(title: string, location: any, id: string): string { if (!title || !id) return id; // Fallback to ID if no title const titleSlug = title .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-') .substring(0, 50); let locationSlug = ''; if (location) { const locationName = location.name || location.county || ''; const state = location.state || ''; if (locationName) { locationSlug = locationName .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/\s+/g, '-') .replace(/-+/g, '-'); } if (state) { locationSlug = locationSlug ? `${locationSlug}-${state.toLowerCase()}` : state.toLowerCase(); } } const shortId = id.substring(0, 8); const parts = [titleSlug, locationSlug, shortId].filter(Boolean); return parts.join('-').replace(/-+/g, '-').replace(/^-|-$/g, '').toLowerCase(); } async function migrateBusinessSlugs(db: NodePgDatabase) { console.log('šŸ”„ Migrating Business Listings...'); // Get all businesses without slugs const businesses = await db .select({ id: schema.businesses_json.id, email: schema.businesses_json.email, data: schema.businesses_json.data, }) .from(schema.businesses_json); let updated = 0; let skipped = 0; for (const business of businesses) { const data = business.data as any; // Skip if slug already exists if (data.slug) { skipped++; continue; } const slug = generateSlug(data.title || '', data.location || {}, business.id); // Update with new slug const updatedData = { ...data, slug }; await db .update(schema.businesses_json) .set({ data: updatedData }) .where(eq(schema.businesses_json.id, business.id)); console.log(` āœ“ ${data.title?.substring(0, 40)}... → ${slug}`); updated++; } console.log(`āœ… Business Listings: ${updated} updated, ${skipped} skipped (already had slugs)`); return updated; } async function migrateCommercialSlugs(db: NodePgDatabase) { console.log('\nšŸ”„ Migrating Commercial Properties...'); // Get all commercial properties without slugs const properties = await db .select({ id: schema.commercials_json.id, email: schema.commercials_json.email, data: schema.commercials_json.data, }) .from(schema.commercials_json); let updated = 0; let skipped = 0; for (const property of properties) { const data = property.data as any; // Skip if slug already exists if (data.slug) { skipped++; continue; } const slug = generateSlug(data.title || '', data.location || {}, property.id); // Update with new slug const updatedData = { ...data, slug }; await db .update(schema.commercials_json) .set({ data: updatedData }) .where(eq(schema.commercials_json.id, property.id)); console.log(` āœ“ ${data.title?.substring(0, 40)}... → ${slug}`); updated++; } console.log(`āœ… Commercial Properties: ${updated} updated, ${skipped} skipped (already had slugs)`); return updated; } async function main() { console.log('═══════════════════════════════════════════════════════'); console.log(' SEO SLUG MIGRATION SCRIPT'); console.log('═══════════════════════════════════════════════════════\n'); // Connect to database const connectionString = process.env.DATABASE_URL || 'postgresql://postgres:postgres@localhost:5432/bizmatch'; console.log(`šŸ“” Connecting to database...`); const pool = new Pool({ connectionString }); const db = drizzle(pool, { schema }); try { const businessCount = await migrateBusinessSlugs(db); const commercialCount = await migrateCommercialSlugs(db); console.log('\n═══════════════════════════════════════════════════════'); console.log(`šŸŽ‰ Migration complete! Total: ${businessCount + commercialCount} listings updated`); console.log('═══════════════════════════════════════════════════════\n'); } catch (error) { console.error('āŒ Migration failed:', error); process.exit(1); } finally { await pool.end(); } } main();