From 443d92804ebb85cae993ad9f58f77d47b312221b Mon Sep 17 00:00:00 2001 From: sebastjanartic <45803536-sebastjanartic@users.noreply.replit.com> Date: Thu, 28 Aug 2025 17:18:48 +0000 Subject: [PATCH] Migrate all videos from Bunny.net to the database Introduces a migration script to import all videos from Bunny.net into the PostgreSQL database, populating the `videos` table with relevant metadata and handling both new and existing video entries. Updates schema to include Bunny.net specific fields. Replit-Commit-Author: Agent Replit-Commit-Session-Id: d7424866-83d1-4486-a212-ac12b4c7becf Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/8cc42625-c1f5-4e43-99bd-77f2c4dedee2/d7424866-83d1-4486-a212-ac12b4c7becf/t16ASiD --- server/bunnyMigration.ts | 156 +++++++++++++++++++++++++++++++++++++++ shared/schema.ts | 7 +- 2 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 server/bunnyMigration.ts diff --git a/server/bunnyMigration.ts b/server/bunnyMigration.ts new file mode 100644 index 0000000..0acd830 --- /dev/null +++ b/server/bunnyMigration.ts @@ -0,0 +1,156 @@ +import { db } from "./db"; +import { videos } from "@shared/schema"; +import { eq } from "drizzle-orm"; +import { bunnyService } from "./bunny"; + +export class BunnyMigrationService { + private migrationInProgress = false; + + async migrateAllVideosToDatabase(): Promise { + if (this.migrationInProgress) { + console.log("🔄 Migration already in progress, skipping..."); + return; + } + + this.migrationInProgress = true; + console.log("🔄 Starting migration of all Bunny.net videos to database..."); + + try { + // Fetch all videos from Bunny.net + const bunnyVideos = await bunnyService.getAllVideos(); + console.log(`📥 Found ${bunnyVideos.length} videos in Bunny.net`); + + let insertedCount = 0; + let updatedCount = 0; + let skippedCount = 0; + + for (const bunnyVideo of bunnyVideos) { + try { + // Check if video already exists in database + const existingVideo = await db + .select() + .from(videos) + .where(eq(videos.id, bunnyVideo.id)) + .limit(1); + + const videoData = { + id: bunnyVideo.id, + title: bunnyVideo.title, + description: bunnyVideo.description || "", + thumbnailUrl: bunnyVideo.thumbnailUrl, + videoUrl: bunnyVideo.videoUrl, + videoUrlMp4: bunnyVideo.videoUrlMp4, + videoUrlIframe: bunnyVideo.videoUrlIframe, + duration: bunnyVideo.duration, + views: bunnyVideo.views, + category: bunnyVideo.category || "", + tags: bunnyVideo.tags || [], + isPublic: true, + uploadStatus: "completed", + originalFileName: bunnyVideo.title, + bunnyId: bunnyVideo.id, + bunnyLibraryId: process.env.BUNNY_LIBRARY_ID || "476412", + source: "bunny", + updatedAt: new Date(), + }; + + if (existingVideo.length === 0) { + // Insert new video + await db.insert(videos).values({ + ...videoData, + createdAt: bunnyVideo.createdAt ? new Date(bunnyVideo.createdAt) : new Date(), + }); + insertedCount++; + } else { + // Update existing video + await db + .update(videos) + .set(videoData) + .where(eq(videos.id, bunnyVideo.id)); + updatedCount++; + } + } catch (error) { + console.error(`❌ Error processing video ${bunnyVideo.title}:`, error); + skippedCount++; + } + } + + console.log(`✅ Migration completed:`); + console.log(` 📥 Inserted: ${insertedCount} videos`); + console.log(` 🔄 Updated: ${updatedCount} videos`); + console.log(` ⚠️ Skipped: ${skippedCount} videos`); + + } catch (error) { + console.error("❌ Migration failed:", error); + throw error; + } finally { + this.migrationInProgress = false; + } + } + + async syncVideoFromBunny(bunnyVideoId: string): Promise { + try { + const bunnyVideo = await bunnyService.getVideo(bunnyVideoId); + if (!bunnyVideo) { + console.warn(`⚠️ Video ${bunnyVideoId} not found in Bunny.net`); + return; + } + + const videoData = { + id: bunnyVideo.id, + title: bunnyVideo.title, + description: bunnyVideo.description || "", + thumbnailUrl: bunnyVideo.thumbnailUrl, + videoUrl: bunnyVideo.videoUrl, + videoUrlMp4: bunnyVideo.videoUrlMp4, + videoUrlIframe: bunnyVideo.videoUrlIframe, + duration: bunnyVideo.duration, + views: bunnyVideo.views, + category: bunnyVideo.category || "", + tags: bunnyVideo.tags || [], + isPublic: true, + uploadStatus: "completed", + originalFileName: bunnyVideo.title, + bunnyId: bunnyVideo.id, + bunnyLibraryId: process.env.BUNNY_LIBRARY_ID || "476412", + source: "bunny", + updatedAt: new Date(), + }; + + // Upsert the video + await db + .insert(videos) + .values({ + ...videoData, + createdAt: bunnyVideo.createdAt ? new Date(bunnyVideo.createdAt) : new Date(), + }) + .onConflictDoUpdate({ + target: videos.id, + set: videoData, + }); + + console.log(`✅ Synced video: ${bunnyVideo.title}`); + } catch (error) { + console.error(`❌ Error syncing video ${bunnyVideoId}:`, error); + } + } + + async getVideoCount(): Promise<{ database: number; bunny: number }> { + try { + const [dbCount, bunnyVideos] = await Promise.all([ + db.select().from(videos).then(result => result.length), + bunnyService.getAllVideos() + ]); + + return { + database: dbCount, + bunny: bunnyVideos.length + }; + } catch (error) { + console.error("❌ Error getting video count:", error); + return { database: 0, bunny: 0 }; + } + } +} + +export const bunnyMigration = new BunnyMigrationService(); \ No newline at end of file diff --git a/shared/schema.ts b/shared/schema.ts index d4ae45e..87b664b 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -4,7 +4,7 @@ import { createInsertSchema } from "drizzle-zod"; import { z } from "zod"; export const videos = pgTable("videos", { - id: varchar("id").primaryKey().default(sql`gen_random_uuid()`), + id: varchar("id").primaryKey(), title: text("title").notNull(), description: text("description").default("").notNull(), thumbnailUrl: text("thumbnail_url").notNull(), @@ -17,13 +17,16 @@ export const videos = pgTable("videos", { category: text("category").default("").notNull(), tags: text("tags").array().default([]).notNull(), isPublic: boolean("is_public").default(true).notNull(), - uploadStatus: text("upload_status").default("pending").notNull(), // pending, processing, completed, failed + uploadStatus: text("upload_status").default("completed").notNull(), // pending, processing, completed, failed originalFileName: text("original_file_name"), fileSize: integer("file_size"), // in bytes bitrate: integer("bitrate"), // in kbps resolution: text("resolution"), // e.g., "1920x1080" format: text("format"), // e.g., "mp4", "avi", "mov" encoding: text("encoding"), // e.g., "h264", "h265" + bunnyId: varchar("bunny_id"), // Original Bunny.net video ID + bunnyLibraryId: varchar("bunny_library_id"), // Bunny.net library ID + source: text("source").default("bunny").notNull(), // bunny, upload, manual createdAt: timestamp("created_at").notNull().default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at").notNull().default(sql`CURRENT_TIMESTAMP`), });