import bcrypt from "bcryptjs"; import { drizzle } from "drizzle-orm/postgres-js"; import { eq, sql } from "drizzle-orm"; import postgres from "postgres"; import * as schema from "../src/server/db/schema"; // Database connection const connectionString = process.env.DATABASE_URL!; const connection = postgres(connectionString); const db = drizzle(connection, { schema }); // Repository sync helper async function syncRepository( repoId: string, repoUrl: string, ): Promise { try { console.log(`๐Ÿ”„ Syncing repository: ${repoUrl}`); // Use localhost for development const devUrl = repoUrl.includes("core.hristudio.com") ? "http://localhost:3000/hristudio-core" : repoUrl; // Fetch repository metadata const repoResponse = await fetch(`${devUrl}/repository.json`); if (!repoResponse.ok) { throw new Error( `Failed to fetch repository metadata: ${repoResponse.status}`, ); } const repoMetadata = (await repoResponse.json()) as { description?: string; author?: { name?: string }; urls?: { git?: string }; trust?: string; }; // For core repository, create a single plugin with all block groups if (repoUrl.includes("core.hristudio.com")) { const indexResponse = await fetch(`${devUrl}/plugins/index.json`); if (!indexResponse.ok) { throw new Error( `Failed to fetch plugin index: ${indexResponse.status}`, ); } const indexData = (await indexResponse.json()) as { plugins?: Array<{ blockCount?: number }>; }; // Create core system plugin await db.insert(schema.plugins).values({ robotId: null, name: "HRIStudio Core System", version: "1.0.0", description: repoMetadata.description ?? "", author: repoMetadata.author?.name ?? "Unknown", repositoryUrl: repoMetadata.urls?.git ?? "", trustLevel: (repoMetadata.trust as "official" | "verified" | "community") ?? "community", status: "active", actionDefinitions: [], metadata: { platform: "Core", category: "system", repositoryId: repoId, blockGroups: indexData.plugins ?? [], totalBlocks: indexData.plugins?.reduce( (sum: number, p: { blockCount?: number }) => sum + (p.blockCount ?? 0), 0, ) ?? 0, }, }); console.log( `โœ… Synced core system with ${indexData.plugins?.length ?? 0} block groups`, ); return 1; } // For robot repositories, sync individual plugins const pluginIndexResponse = await fetch(`${devUrl}/plugins/index.json`); if (!pluginIndexResponse.ok) { throw new Error( `Failed to fetch plugin index: ${pluginIndexResponse.status}`, ); } const pluginFiles = (await pluginIndexResponse.json()) as string[]; let syncedCount = 0; for (const pluginFile of pluginFiles) { try { const pluginResponse = await fetch(`${devUrl}/plugins/${pluginFile}`); if (!pluginResponse.ok) { console.warn( `Failed to fetch ${pluginFile}: ${pluginResponse.status}`, ); continue; } const pluginData = (await pluginResponse.json()) as { name?: string; version?: string; description?: string; manufacturer?: { name?: string }; documentation?: { mainUrl?: string }; trustLevel?: string; actions?: unknown[]; platform?: string; category?: string; specs?: unknown; ros2Config?: unknown; }; await db.insert(schema.plugins).values({ robotId: null, // Will be matched later if needed name: pluginData.name ?? pluginFile.replace(".json", ""), version: pluginData.version ?? "1.0.0", description: pluginData.description ?? "", author: pluginData.manufacturer?.name ?? repoMetadata.author?.name ?? "Unknown", repositoryUrl: pluginData.documentation?.mainUrl ?? repoMetadata.urls?.git ?? "", trustLevel: (pluginData.trustLevel as "official" | "verified" | "community") ?? (repoMetadata.trust as "official" | "verified" | "community") ?? "community", status: "active", actionDefinitions: pluginData.actions ?? [], metadata: { platform: pluginData.platform, category: pluginData.category, repositoryId: repoId, specs: pluginData.specs, ros2Config: pluginData.ros2Config, }, }); console.log(`โœ… Synced plugin: ${pluginData.name}`); syncedCount++; } catch (error) { console.warn(`Failed to process ${pluginFile}:`, error); } } return syncedCount; } catch (error) { console.error(`Failed to sync repository ${repoUrl}:`, error); return 0; } } async function main() { console.log("๐ŸŒฑ Starting simplified seed script..."); try { // Clean existing data (in reverse order of dependencies) console.log("๐Ÿงน Cleaning existing data..."); await db.delete(schema.studyPlugins).where(sql`1=1`); await db.delete(schema.plugins).where(sql`1=1`); await db.delete(schema.pluginRepositories).where(sql`1=1`); await db.delete(schema.trialEvents).where(sql`1=1`); await db.delete(schema.trials).where(sql`1=1`); await db.delete(schema.steps).where(sql`1=1`); await db.delete(schema.experiments).where(sql`1=1`); await db.delete(schema.participants).where(sql`1=1`); await db.delete(schema.studyMembers).where(sql`1=1`); await db.delete(schema.userSystemRoles).where(sql`1=1`); await db.delete(schema.studies).where(sql`1=1`); await db.delete(schema.users).where(sql`1=1`); await db.delete(schema.robots).where(sql`1=1`); // Create robots console.log("๐Ÿค– Creating robots..."); const robots = [ { name: "TurtleBot3 Burger", manufacturer: "ROBOTIS", model: "TurtleBot3 Burger", description: "A compact, affordable, programmable, ROS2-based mobile robot for education and research", capabilities: ["differential_drive", "lidar", "imu", "odometry"], communicationProtocol: "ros2" as const, }, { name: "NAO Humanoid Robot", manufacturer: "SoftBank Robotics", model: "NAO V6", description: "Humanoid robot designed for education, research, and social interaction", capabilities: ["speech", "vision", "walking", "gestures"], communicationProtocol: "rest" as const, }, ]; const insertedRobots = await db .insert(schema.robots) .values(robots) .returning(); console.log(`โœ… Created ${insertedRobots.length} robots`); // Create users console.log("๐Ÿ‘ฅ Creating users..."); const hashedPassword = await bcrypt.hash("password123", 12); const users = [ { name: "Sean O'Connor", email: "sean@soconnor.dev", password: hashedPassword, emailVerified: new Date(), image: null, }, { name: "Dr. Alice Rodriguez", email: "alice.rodriguez@university.edu", password: hashedPassword, emailVerified: new Date(), image: null, }, { name: "Dr. Bob Chen", email: "bob.chen@research.org", password: hashedPassword, emailVerified: new Date(), image: null, }, { name: "Emily Watson", email: "emily.watson@lab.edu", password: hashedPassword, emailVerified: new Date(), image: null, }, { name: "Dr. Maria Santos", email: "maria.santos@tech.edu", password: hashedPassword, emailVerified: new Date(), image: null, }, ]; const insertedUsers = await db .insert(schema.users) .values(users) .returning(); console.log(`โœ… Created ${insertedUsers.length} users`); // Assign system roles console.log("๐ŸŽญ Assigning system roles..."); const seanUser = insertedUsers.find((u) => u.email === "sean@soconnor.dev"); if (!seanUser) { throw new Error("Sean user not found after creation"); } await db.insert(schema.userSystemRoles).values({ userId: seanUser.id, role: "administrator", }); console.log(`โœ… Assigned administrator role to Sean`); // Create plugin repositories console.log("๐Ÿ“ฆ Creating plugin repositories..."); const repositories = [ { name: "HRIStudio Core System Blocks", url: "https://core.hristudio.com", description: "Essential system blocks for experiment design including events, control flow, wizard actions, and logic operations", trustLevel: "official" as const, isEnabled: true, isOfficial: true, syncStatus: "pending" as const, createdBy: seanUser.id, }, { name: "HRIStudio Official Robot Plugins", url: "https://repo.hristudio.com", description: "Official collection of robot plugins maintained by the HRIStudio team", trustLevel: "official" as const, isEnabled: true, isOfficial: true, syncStatus: "pending" as const, createdBy: seanUser.id, }, ]; const insertedRepos = await db .insert(schema.pluginRepositories) .values(repositories) .returning(); console.log(`โœ… Created ${insertedRepos.length} plugin repositories`); // Sync repositories to populate plugins console.log("๐Ÿ”„ Syncing plugin repositories..."); let totalPlugins = 0; for (const repo of insertedRepos) { const syncedCount = await syncRepository(repo.id, repo.url); totalPlugins += syncedCount; // Update sync status await db .update(schema.pluginRepositories) .set({ syncStatus: syncedCount > 0 ? "completed" : "failed", lastSyncAt: new Date(), }) .where(eq(schema.pluginRepositories.id, repo.id)); } // Create studies console.log("๐Ÿ“š Creating studies..."); const studies = [ { name: "Human-Robot Collaboration Study", description: "Investigating collaborative tasks between humans and robots in shared workspace environments", institution: "MIT Computer Science", irbProtocol: "IRB-2024-001", status: "active" as const, createdBy: seanUser.id, }, { name: "Robot Navigation Study", description: "A comprehensive study on robot navigation and obstacle avoidance in dynamic environments", institution: "Stanford HCI Lab", irbProtocol: "IRB-2024-002", status: "draft" as const, createdBy: seanUser.id, }, { name: "Social Robot Interaction Study", description: "Examining social dynamics between humans and humanoid robots in educational settings", institution: "Carnegie Mellon", irbProtocol: "IRB-2024-003", status: "active" as const, createdBy: seanUser.id, }, ]; const insertedStudies = await db .insert(schema.studies) .values(studies) .returning(); console.log(`โœ… Created ${insertedStudies.length} studies`); // Create study memberships console.log("๐Ÿ‘ฅ Creating study memberships..."); const studyMemberships = []; // Sean as owner of all studies for (const study of insertedStudies) { studyMemberships.push({ studyId: study.id, userId: seanUser.id, role: "owner" as const, }); } // Add other users as researchers/wizards const otherUsers = insertedUsers.filter((u) => u.id !== seanUser.id); if (otherUsers.length > 0 && insertedStudies[0]) { studyMemberships.push({ studyId: insertedStudies[0].id, userId: otherUsers[0]!.id, role: "researcher" as const, }); if (otherUsers.length > 1 && insertedStudies[1]) { studyMemberships.push({ studyId: insertedStudies[1].id, userId: otherUsers[1]!.id, role: "wizard" as const, }); } } await db.insert(schema.studyMembers).values(studyMemberships); console.log(`โœ… Created ${studyMemberships.length} study memberships`); // Install core plugin in all studies console.log("๐Ÿ”Œ Installing core plugin in all studies..."); const corePlugin = await db .select() .from(schema.plugins) .where(eq(schema.plugins.name, "HRIStudio Core System")) .limit(1); if (corePlugin.length > 0) { const coreInstallations = insertedStudies.map((study) => ({ studyId: study.id, pluginId: corePlugin[0]!.id, configuration: {}, installedBy: seanUser.id, })); await db.insert(schema.studyPlugins).values(coreInstallations); console.log( `โœ… Installed core plugin in ${insertedStudies.length} studies`, ); } // Create some participants console.log("๐Ÿ‘ค Creating participants..."); const participants = []; for (let i = 0; i < insertedStudies.length; i++) { const study = insertedStudies[i]; if (study) { participants.push( { studyId: study.id, participantCode: `P${String(i * 2 + 1).padStart(3, "0")}`, name: `Participant ${i * 2 + 1}`, email: `participant${i * 2 + 1}@example.com`, demographics: { age: 25 + i, gender: "prefer not to say" }, consentGiven: true, consentGivenAt: new Date(), }, { studyId: study.id, participantCode: `P${String(i * 2 + 2).padStart(3, "0")}`, name: `Participant ${i * 2 + 2}`, email: `participant${i * 2 + 2}@example.com`, demographics: { age: 30 + i, gender: "prefer not to say" }, consentGiven: true, consentGivenAt: new Date(), }, ); } } const insertedParticipants = await db .insert(schema.participants) .values(participants) .returning(); console.log(`โœ… Created ${insertedParticipants.length} participants`); // Create basic experiments console.log("๐Ÿงช Creating experiments..."); const experiments = insertedStudies.map((study, i) => ({ studyId: study.id, name: `Basic Interaction Protocol ${i + 1}`, description: `A simple human-robot interaction experiment for ${study.name}`, version: 1, status: "ready" as const, estimatedDuration: 30 + i * 10, createdBy: seanUser.id, })); const insertedExperiments = await db .insert(schema.experiments) .values(experiments) .returning(); console.log(`โœ… Created ${insertedExperiments.length} experiments`); // Create some trials for dashboard demo console.log("๐Ÿงช Creating sample trials..."); const trials = []; for (const experiment of insertedExperiments) { if (!experiment) continue; const studyParticipants = insertedParticipants.filter( (p) => p.studyId === experiment.studyId, ); if (studyParticipants.length > 0) { // Create 2-3 trials per experiment const trialCount = Math.min(studyParticipants.length, 3); for (let j = 0; j < trialCount; j++) { const participant = studyParticipants[j]; if (participant) { const scheduledAt = new Date( Date.now() - Math.random() * 2 * 24 * 60 * 60 * 1000, ); const startedAt = new Date(scheduledAt.getTime() + 30 * 60 * 1000); // 30 minutes after scheduled const completedAt = new Date(startedAt.getTime() + 45 * 60 * 1000); // 45 minutes after started // Vary the status: some completed, some in progress, some scheduled let status: "scheduled" | "in_progress" | "completed" | "aborted"; let actualStartedAt = null; let actualCompletedAt = null; if (j === 0) { status = "completed"; actualStartedAt = startedAt; actualCompletedAt = completedAt; } else if (j === 1 && trialCount > 2) { status = "in_progress"; actualStartedAt = startedAt; } else { status = "scheduled"; } trials.push({ participantId: participant.id, experimentId: experiment.id, sessionNumber: j + 1, status, scheduledAt, startedAt: actualStartedAt, completedAt: actualCompletedAt, notes: `Trial session ${j + 1} for ${experiment.name}`, createdBy: seanUser.id, }); } } } } const insertedTrials = await db .insert(schema.trials) .values(trials) .returning(); console.log(`โœ… Created ${insertedTrials.length} trials`); // Create some activity logs for dashboard demo console.log("๐Ÿ“ Creating activity logs..."); const activityEntries = []; // Study creation activities for (const study of insertedStudies) { activityEntries.push({ studyId: study.id, userId: seanUser.id, action: "study_created", description: `Created study "${study.name}"`, createdAt: new Date( Date.now() - Math.random() * 7 * 24 * 60 * 60 * 1000, ), // Random time in last week }); } // Experiment creation activities for (const experiment of insertedExperiments) { activityEntries.push({ studyId: experiment.studyId, userId: seanUser.id, action: "experiment_created", description: `Created experiment protocol "${experiment.name}"`, createdAt: new Date( Date.now() - Math.random() * 5 * 24 * 60 * 60 * 1000, ), // Random time in last 5 days }); } // Participant enrollment activities for (const participant of insertedParticipants) { activityEntries.push({ studyId: participant.studyId, userId: seanUser.id, action: "participant_enrolled", description: `Enrolled participant ${participant.participantCode}`, createdAt: new Date( Date.now() - Math.random() * 3 * 24 * 60 * 60 * 1000, ), // Random time in last 3 days }); } // Plugin installation activities for (const study of insertedStudies) { activityEntries.push({ studyId: study.id, userId: seanUser.id, action: "plugin_installed", description: "Installed HRIStudio Core System plugin", createdAt: new Date( Date.now() - Math.random() * 2 * 24 * 60 * 60 * 1000, ), // Random time in last 2 days }); } // Add some recent activities const firstStudy = insertedStudies[0]; if (firstStudy) { activityEntries.push( { studyId: firstStudy.id, userId: seanUser.id, action: "trial_scheduled", description: "Scheduled new trial session", createdAt: new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago }, { studyId: firstStudy.id, userId: seanUser.id, action: "experiment_updated", description: "Updated experiment parameters", createdAt: new Date(Date.now() - 4 * 60 * 60 * 1000), // 4 hours ago }, ); } const insertedActivity = await db .insert(schema.activityLogs) .values(activityEntries) .returning(); console.log(`โœ… Created ${insertedActivity.length} activity log entries`); console.log("\nโœ… Seed script completed successfully!"); console.log("\n๐Ÿ“Š Created:"); console.log(` โ€ข ${insertedRobots.length} robots`); console.log(` โ€ข ${insertedUsers.length} users`); console.log(` โ€ข ${insertedRepos.length} plugin repositories`); console.log(` โ€ข ${totalPlugins} plugins (via repository sync)`); console.log(` โ€ข ${insertedStudies.length} studies`); console.log(` โ€ข ${studyMemberships.length} study memberships`); console.log(` โ€ข ${insertedParticipants.length} participants`); console.log(` โ€ข ${insertedExperiments.length} experiments`); console.log(` โ€ข ${insertedTrials.length} trials`); console.log("\n๐Ÿ‘ค Login credentials:"); console.log(" Email: sean@soconnor.dev"); console.log(" Password: password123"); console.log(" Role: Administrator"); console.log("\n๐Ÿ”„ Plugin repositories synced:"); for (const repo of insertedRepos) { console.log(` โ€ข ${repo.name}: ${repo.url}`); } console.log("\n๐ŸŽฏ Next steps:"); console.log(" 1. Start the development server: bun dev"); console.log(" 2. Access admin dashboard to manage repositories"); console.log(" 3. Browse plugin store to see synced plugins"); } catch (error) { console.error("โŒ Error running seed script:", error); throw error; } finally { await connection.end(); } } if (import.meta.url === `file://${process.argv[1]}`) { void main(); } export default main;