import { db } from "./db"; import { sql } from "drizzle-orm"; import { videoSyncService } from "./videoSync"; export class VideoMigrator { private isRunning = false; private syncInterval: NodeJS.Timeout | null = null; async initialize(): Promise { console.log("🔄 Initializing video metadata migrator..."); // Run initial migration await this.migrateAllVideoMetadata(); // Set up periodic sync every 5 minutes to ensure database is always complete this.syncInterval = setInterval(async () => { console.log("⏰ Starting periodic video metadata sync..."); try { await this.migrateAllVideoMetadata(); const count = await this.getVideoCount(); console.log(`✅ Periodic sync completed - Database: ${count.database}, Bunny: ${count.bunny}`); } catch (error) { console.error("❌ Periodic sync failed:", error); } }, 5 * 60 * 1000); // Every 5 minutes console.log("✅ Video metadata migrator initialized with periodic sync"); } async migrateAllVideoMetadata(): Promise { if (this.isRunning) { console.log("🔄 Migration already running..."); return; } this.isRunning = true; console.log("🔄 Migrating all video metadata from Bunny.net to PostgreSQL..."); try { // Get all videos from cache const videoList = videoSyncService.getVideos(1000, 0).videos; console.log(`📥 Found ${videoList.length} videos in cache`); let inserted = 0; let updated = 0; for (const video of videoList) { try { // Upsert video metadata with simple SQL await db.execute(sql` INSERT INTO video_metadata ( id, title, description, thumbnail_url, video_url, duration, views, category, created_at, updated_at ) VALUES ( ${video.id}, ${video.title}, ${video.description || ""}, ${video.thumbnailUrl}, ${video.videoUrl}, ${video.duration}, ${video.views}, ${video.category || ""}, ${video.createdAt ? new Date(video.createdAt) : new Date()}, ${new Date()} ) ON CONFLICT (id) DO UPDATE SET title = EXCLUDED.title, description = EXCLUDED.description, thumbnail_url = EXCLUDED.thumbnail_url, video_url = EXCLUDED.video_url, duration = EXCLUDED.duration, views = EXCLUDED.views, category = EXCLUDED.category, updated_at = EXCLUDED.updated_at `); inserted++; // Log progress every 10 videos if ((inserted + updated) % 10 === 0) { console.log(`📊 Progress: ${inserted + updated}/${videoList.length} videos processed`); } } catch (error) { console.error(`❌ Error processing ${video.title}:`, error); } } const dbCount = await this.getVideoCount(); console.log(`✅ Migration completed:`); console.log(` 📥 Inserted: ${inserted} new videos`); console.log(` 🔄 Updated: ${updated} existing videos`); console.log(` 📊 Database total: ${dbCount.database} videos`); console.log(` 🎥 Bunny.net total: ${dbCount.bunny} videos`); // Verify all Bunny videos are in database if (dbCount.database < dbCount.bunny) { console.warn(`⚠️ Warning: Database has ${dbCount.database} videos but Bunny.net has ${dbCount.bunny}. Some videos may be missing!`); } else { console.log("✅ All Bunny.net videos are synchronized with database"); } } catch (error) { console.error("❌ Migration failed:", error); } finally { this.isRunning = false; } } async getVideoCount(): Promise<{ database: number; bunny: number }> { try { const dbResult = await db.execute(sql`SELECT COUNT(*) FROM video_metadata`); const cachedVideos = videoSyncService.getVideos(1000, 0).videos; return { database: Number(dbResult.rows[0]?.count || 0), bunny: cachedVideos.length }; } catch (error) { console.error("❌ Error counting videos:", error); return { database: 0, bunny: 0 }; } } } export const videoMigrator = new VideoMigrator();