/** * 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); });