Spaces:
Runtime error
Runtime error
| /** | |
| * Seed script for local testing without real YouTube videos. | |
| * Inserts fake videos, detected objects, and marketplace matches | |
| * tied to your connected YouTube channel. | |
| * | |
| * Usage: npx tsx scripts/seed-test-data.ts | |
| */ | |
| import { drizzle } from "drizzle-orm/postgres-js"; | |
| import postgres from "postgres"; | |
| import * as dotenv from "dotenv"; | |
| import * as schema from "../src/lib/db/schema"; | |
| const { youtubeVideos, detectedObjects, marketplaceMatches } = schema; | |
| dotenv.config({ path: ".env.local" }); | |
| const run = async () => { | |
| const connectionString = process.env.DIRECT_URL; | |
| if (!connectionString) throw new Error("DIRECT_URL not set in .env.local"); | |
| const sql = postgres(connectionString, { max: 1 }); | |
| const db = drizzle(sql, { schema, casing: "snake_case" }); | |
| // 1. Find the connected channel | |
| const channel = await db.query.youtubeChannels.findFirst(); | |
| if (!channel) { | |
| console.error("❌ No YouTube channel found. Connect one on the dashboard first."); | |
| await sql.end(); | |
| process.exit(1); | |
| } | |
| console.log(`✅ Found channel: ${channel.channelName} (${channel.id})`); | |
| // 2. Insert fake videos | |
| const videos = [ | |
| { videoId: "seed-vid-001", title: "Tech Gadget Unboxing", duration: "PT12M30S", viewCount: 4200 }, | |
| { videoId: "seed-vid-002", title: "Home Office Setup Tour", duration: "PT8M15S", viewCount: 1800 }, | |
| { videoId: "seed-vid-003", title: "Fashion Haul 2024", duration: "PT15M00S", viewCount: 9100 }, | |
| { videoId: "seed-vid-004", title: "Audio Equipment Review", duration: "PT20M45S", viewCount: 3300 }, | |
| { videoId: "seed-vid-005", title: "Living Room Makeover", duration: "PT18M10S", viewCount: 5600 }, | |
| ]; | |
| const insertedVideos = await db | |
| .insert(youtubeVideos) | |
| .values( | |
| videos.map((v) => ({ | |
| channelId: channel.id, | |
| videoId: v.videoId, | |
| title: v.title, | |
| description: `Seed data: ${v.title}`, | |
| thumbnailUrl: `https://i.ytimg.com/vi/${v.videoId}/mqdefault.jpg`, | |
| duration: v.duration, | |
| viewCount: v.viewCount, | |
| publishedAt: new Date("2024-11-15T10:00:00Z"), | |
| })) | |
| ) | |
| .onConflictDoNothing() | |
| .returning(); | |
| console.log(`✅ Inserted ${insertedVideos.length} videos`); | |
| // Re-fetch all seed videos (in case they already existed) | |
| const allVideos = await db.query.youtubeVideos.findMany({ | |
| where: (t, { inArray }) => inArray(t.videoId, videos.map((v) => v.videoId)), | |
| }); | |
| // 3. Insert detected objects (mix of statuses for testing moderation) | |
| const objects: Array<{ | |
| videoId: string; | |
| objectName: string; | |
| category: "Tech" | "Fashion" | "Furniture" | "Audio" | "Other"; | |
| moderationStatus: "PENDING" | "APPROVED" | "REJECTED"; | |
| confidenceScore: number; | |
| frameTimestamp: number; | |
| }> = [ | |
| { videoId: "seed-vid-001", objectName: "Wireless Headphones", category: "Audio", moderationStatus: "APPROVED", confidenceScore: 0.94, frameTimestamp: 45 }, | |
| { videoId: "seed-vid-001", objectName: "USB-C Hub", category: "Tech", moderationStatus: "APPROVED", confidenceScore: 0.88, frameTimestamp: 120 }, | |
| { videoId: "seed-vid-001", objectName: "Mechanical Keyboard", category: "Tech", moderationStatus: "PENDING", confidenceScore: 0.76, frameTimestamp: 200 }, | |
| { videoId: "seed-vid-002", objectName: "Standing Desk", category: "Furniture", moderationStatus: "APPROVED", confidenceScore: 0.91, frameTimestamp: 30 }, | |
| { videoId: "seed-vid-002", objectName: "Ergonomic Chair", category: "Furniture", moderationStatus: "APPROVED", confidenceScore: 0.89, frameTimestamp: 60 }, | |
| { videoId: "seed-vid-002", objectName: "Monitor Light Bar", category: "Tech", moderationStatus: "REJECTED", confidenceScore: 0.62, frameTimestamp: 90 }, | |
| { videoId: "seed-vid-003", objectName: "Leather Jacket", category: "Fashion", moderationStatus: "APPROVED", confidenceScore: 0.95, frameTimestamp: 15 }, | |
| { videoId: "seed-vid-003", objectName: "Running Shoes", category: "Fashion", moderationStatus: "PENDING", confidenceScore: 0.71, frameTimestamp: 180 }, | |
| { videoId: "seed-vid-004", objectName: "Studio Microphone", category: "Audio", moderationStatus: "APPROVED", confidenceScore: 0.93, frameTimestamp: 55 }, | |
| { videoId: "seed-vid-004", objectName: "Audio Interface", category: "Audio", moderationStatus: "APPROVED", confidenceScore: 0.87, frameTimestamp: 110 }, | |
| { videoId: "seed-vid-005", objectName: "Throw Pillows", category: "Furniture", moderationStatus: "APPROVED", confidenceScore: 0.82, frameTimestamp: 25 }, | |
| { videoId: "seed-vid-005", objectName: "Floor Lamp", category: "Furniture", moderationStatus: "PENDING", confidenceScore: 0.74, frameTimestamp: 95 }, | |
| ]; | |
| const videoMap = new Map(allVideos.map((v) => [v.videoId, v.id])); | |
| const insertedObjects = await db | |
| .insert(detectedObjects) | |
| .values( | |
| objects.map((o) => ({ | |
| videoId: videoMap.get(o.videoId)!, | |
| objectName: o.objectName, | |
| category: o.category, | |
| confidenceScore: o.confidenceScore, | |
| frameTimestamp: o.frameTimestamp, | |
| status: "approved" as const, | |
| moderationStatus: o.moderationStatus, | |
| detectionMetadata: { source: "seed", model: "test" }, | |
| })) | |
| ) | |
| .onConflictDoNothing() | |
| .returning(); | |
| console.log(`✅ Inserted ${insertedObjects.length} detected objects`); | |
| // Re-fetch approved objects for marketplace match seeding | |
| const approvedObjects = await db.query.detectedObjects.findMany({ | |
| where: (t, { and, eq: eq_ }) => | |
| and(eq_(t.moderationStatus, "APPROVED")), | |
| }); | |
| // 4. Insert marketplace matches for approved objects | |
| const matchTemplates: Array<{ | |
| marketplace: "amazon" | "ebay" | "etsy"; | |
| productName: string; | |
| price: number; | |
| }> = [ | |
| { marketplace: "amazon", productName: "Sony WH-1000XM5 Headphones", price: 349.99 }, | |
| { marketplace: "amazon", productName: "Anker USB-C 7-in-1 Hub", price: 39.99 }, | |
| { marketplace: "amazon", productName: "FlexiSpot E7 Standing Desk", price: 449.0 }, | |
| { marketplace: "amazon", productName: "Herman Miller Aeron Chair", price: 1199.0 }, | |
| { marketplace: "amazon", productName: "Shure SM7B Microphone", price: 249.0 }, | |
| { marketplace: "ebay", productName: "Audio-Technica AT2020 Mic", price: 89.99 }, | |
| { marketplace: "ebay", productName: "Focusrite Solo Audio Interface", price: 119.99 }, | |
| { marketplace: "etsy", productName: "Handmade Leather Jacket", price: 289.0 }, | |
| { marketplace: "amazon", productName: "IKEA Lack Floor Lamp", price: 24.99 }, | |
| ]; | |
| let matchCount = 0; | |
| for (let i = 0; i < approvedObjects.length && i < matchTemplates.length; i++) { | |
| const obj = approvedObjects[i]; | |
| const tmpl = matchTemplates[i]; | |
| const inserted = await db | |
| .insert(marketplaceMatches) | |
| .values({ | |
| objectId: obj.id, | |
| marketplace: tmpl.marketplace, | |
| productId: `seed-prod-${String(i + 1).padStart(3, "0")}`, | |
| productName: tmpl.productName, | |
| price: tmpl.price, | |
| availabilityStatus: "IN_STOCK", | |
| affiliateUrl: `https://www.amazon.com/dp/seed-${i + 1}`, | |
| }) | |
| .onConflictDoNothing() | |
| .returning(); | |
| matchCount += inserted.length; | |
| } | |
| console.log(`✅ Inserted ${matchCount} marketplace matches`); | |
| console.log("\n🎉 Seed complete. Test pages to try:"); | |
| console.log(` • Dashboard: http://localhost:3000/dashboard`); | |
| console.log(` • Public Vault: http://localhost:3000/vault/${channel.creatorSlug}`); | |
| await sql.end(); | |
| }; | |
| run().catch((err) => { | |
| console.error("❌ Seed failed:", err); | |
| process.exit(1); | |
| }); | |