163 lines
5.7 KiB
TypeScript
163 lines
5.7 KiB
TypeScript
/**
|
|
* 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<typeof schema>) {
|
|
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<typeof schema>) {
|
|
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();
|