import { PrismaClient, TaskStatus, TaskPriority, ProjectStatus, WorkspaceMemberRole, EntryStatus, Visibility, } from "@prisma/client"; const prisma = new PrismaClient(); async function main() { console.log("Seeding database..."); // IMPORTANT: This seed script should not be run concurrently // If running in CI/CD, ensure serialization of seed operations // to prevent race conditions and data corruption // Create test user const user = await prisma.user.upsert({ where: { email: "dev@mosaic.local" }, update: {}, create: { email: "dev@mosaic.local", name: "Development User", preferences: { theme: "system", notifications: true, }, }, }); console.log(`Created user: ${user.email}`); // Create workspace const workspace = await prisma.workspace.upsert({ where: { id: "00000000-0000-0000-0000-000000000001" }, update: {}, create: { id: "00000000-0000-0000-0000-000000000001", name: "Development Workspace", ownerId: user.id, settings: { timezone: "America/New_York", }, }, }); console.log(`Created workspace: ${workspace.name}`); // Add user as workspace owner await prisma.workspaceMember.upsert({ where: { workspaceId_userId: { workspaceId: workspace.id, userId: user.id, }, }, update: {}, create: { workspaceId: workspace.id, userId: user.id, role: WorkspaceMemberRole.OWNER, }, }); // ============================================ // WIDGET DEFINITIONS (global, not workspace-scoped) // ============================================ const widgetDefs = [ { name: "TasksWidget", displayName: "Tasks", description: "View and manage your tasks", component: "TasksWidget", defaultWidth: 2, defaultHeight: 2, minWidth: 1, minHeight: 2, maxWidth: 4, maxHeight: null, configSchema: {}, }, { name: "CalendarWidget", displayName: "Calendar", description: "View upcoming events and schedule", component: "CalendarWidget", defaultWidth: 2, defaultHeight: 2, minWidth: 2, minHeight: 2, maxWidth: 4, maxHeight: null, configSchema: {}, }, { name: "QuickCaptureWidget", displayName: "Quick Capture", description: "Quickly capture notes and tasks", component: "QuickCaptureWidget", defaultWidth: 2, defaultHeight: 1, minWidth: 2, minHeight: 1, maxWidth: 4, maxHeight: 2, configSchema: {}, }, { name: "AgentStatusWidget", displayName: "Agent Status", description: "Monitor agent activity and status", component: "AgentStatusWidget", defaultWidth: 2, defaultHeight: 2, minWidth: 1, minHeight: 2, maxWidth: 3, maxHeight: null, configSchema: {}, }, { name: "ActiveProjectsWidget", displayName: "Active Projects & Agent Chains", description: "View active projects and running agent sessions", component: "ActiveProjectsWidget", defaultWidth: 2, defaultHeight: 3, minWidth: 2, minHeight: 2, maxWidth: 4, maxHeight: null, configSchema: {}, }, { name: "TaskProgressWidget", displayName: "Task Progress", description: "Live progress of orchestrator agent tasks", component: "TaskProgressWidget", defaultWidth: 2, defaultHeight: 2, minWidth: 1, minHeight: 2, maxWidth: 3, maxHeight: null, configSchema: {}, }, { name: "OrchestratorEventsWidget", displayName: "Orchestrator Events", description: "Recent orchestration events with stream/Matrix visibility", component: "OrchestratorEventsWidget", defaultWidth: 2, defaultHeight: 2, minWidth: 1, minHeight: 2, maxWidth: 4, maxHeight: null, configSchema: {}, }, ]; for (const wd of widgetDefs) { await prisma.widgetDefinition.upsert({ where: { name: wd.name }, update: { displayName: wd.displayName, description: wd.description, component: wd.component, defaultWidth: wd.defaultWidth, defaultHeight: wd.defaultHeight, minWidth: wd.minWidth, minHeight: wd.minHeight, maxWidth: wd.maxWidth, maxHeight: wd.maxHeight, configSchema: wd.configSchema, }, create: { name: wd.name, displayName: wd.displayName, description: wd.description, component: wd.component, defaultWidth: wd.defaultWidth, defaultHeight: wd.defaultHeight, minWidth: wd.minWidth, minHeight: wd.minHeight, maxWidth: wd.maxWidth, maxHeight: wd.maxHeight, configSchema: wd.configSchema, }, }); } console.log(`Seeded ${widgetDefs.length} widget definitions`); // Use transaction for atomic seed data reset and creation await prisma.$transaction(async (tx) => { // Delete existing seed data for idempotency (avoids duplicates on re-run) await tx.task.deleteMany({ where: { workspaceId: workspace.id } }); await tx.event.deleteMany({ where: { workspaceId: workspace.id } }); await tx.project.deleteMany({ where: { workspaceId: workspace.id } }); // Create sample project const project = await tx.project.create({ data: { workspaceId: workspace.id, name: "Sample Project", description: "A sample project for development", status: ProjectStatus.ACTIVE, creatorId: user.id, color: "#3B82F6", }, }); console.log(`Created project: ${project.name}`); // Create sample tasks const tasks = [ { title: "Set up development environment", status: TaskStatus.COMPLETED, priority: TaskPriority.HIGH, }, { title: "Review project requirements", status: TaskStatus.IN_PROGRESS, priority: TaskPriority.MEDIUM, }, { title: "Design database schema", status: TaskStatus.COMPLETED, priority: TaskPriority.HIGH, }, { title: "Implement NestJS integration", status: TaskStatus.COMPLETED, priority: TaskPriority.HIGH, }, { title: "Create seed data", status: TaskStatus.IN_PROGRESS, priority: TaskPriority.MEDIUM, }, ]; // Use createMany for batch insertion (better performance) await tx.task.createMany({ data: tasks.map((taskData) => ({ workspaceId: workspace.id, title: taskData.title, status: taskData.status, priority: taskData.priority, creatorId: user.id, projectId: project.id, })), }); console.log(`Created ${tasks.length} sample tasks`); // Create sample event const tomorrow = new Date(); tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setHours(10, 0, 0, 0); await tx.event.create({ data: { workspaceId: workspace.id, title: "Morning standup", description: "Daily team sync", startTime: tomorrow, endTime: new Date(tomorrow.getTime() + 30 * 60000), // 30 minutes later creatorId: user.id, projectId: project.id, }, }); console.log("Created sample event"); // ============================================ // KNOWLEDGE MODULE SEED DATA // ============================================ // Delete existing knowledge data await tx.knowledgeEmbedding.deleteMany({ where: { entry: { workspaceId: workspace.id } } }); await tx.knowledgeEntryTag.deleteMany({ where: { entry: { workspaceId: workspace.id } } }); await tx.knowledgeLink.deleteMany({ where: { source: { workspaceId: workspace.id } } }); await tx.knowledgeEntryVersion.deleteMany({ where: { entry: { workspaceId: workspace.id } } }); await tx.knowledgeEntry.deleteMany({ where: { workspaceId: workspace.id } }); await tx.knowledgeTag.deleteMany({ where: { workspaceId: workspace.id } }); // Create knowledge tags const tags = await Promise.all([ tx.knowledgeTag.create({ data: { workspaceId: workspace.id, name: "Architecture", slug: "architecture", color: "#3B82F6", description: "System architecture and design decisions", }, }), tx.knowledgeTag.create({ data: { workspaceId: workspace.id, name: "Development", slug: "development", color: "#10B981", description: "Development practices and guidelines", }, }), tx.knowledgeTag.create({ data: { workspaceId: workspace.id, name: "Getting Started", slug: "getting-started", color: "#F59E0B", description: "Onboarding and setup guides", }, }), ]); console.log(`Created ${tags.length} knowledge tags`); // Create knowledge entries const entries = [ { slug: "welcome", title: "Welcome to Mosaic Stack Knowledge Base", content: `# Welcome to Mosaic Stack This is the knowledge base for the Mosaic Stack project. Here you'll find: - **[[architecture-overview]]** - High-level system architecture - **[[development-setup]]** - Getting started with development - **[[database-schema]]** - Database design and conventions ## About This Knowledge Base The Knowledge Module provides: - Wiki-style linking between entries - Full-text and semantic search - Version history and change tracking - Tag-based organization Start exploring by following the links above!`, summary: "Introduction to the Mosaic Stack knowledge base and navigation guide", status: EntryStatus.PUBLISHED, visibility: Visibility.WORKSPACE, tags: ["getting-started"], }, { slug: "architecture-overview", title: "Architecture Overview", content: `# Architecture Overview The Mosaic Stack is built on a modern, scalable architecture: ## Stack Components - **Frontend**: Next.js 15+ with React 19 - **Backend**: NestJS with Prisma ORM - **Database**: PostgreSQL 17 with pgvector - **Cache**: Valkey (Redis fork) ## Key Modules 1. **Task Management** - See [[development-setup]] for local setup 2. **Event Calendar** - Integrated scheduling 3. **Knowledge Base** - This module! See [[database-schema]] 4. **Agent Orchestration** - AI agent coordination The database schema is documented in [[database-schema]].`, summary: "High-level overview of Mosaic Stack architecture and components", status: EntryStatus.PUBLISHED, visibility: Visibility.WORKSPACE, tags: ["architecture"], }, { slug: "development-setup", title: "Development Setup Guide", content: `# Development Setup Guide ## Prerequisites - Node.js 22+ - PostgreSQL 17 - pnpm 9+ ## Quick Start \`\`\`bash # Clone the repository git clone https://git.mosaicstack.dev/mosaic/stack.git # Install dependencies pnpm install # Set up database cd apps/api pnpm prisma migrate dev pnpm prisma:seed # Start development servers pnpm dev \`\`\` ## Architecture Before diving in, review the [[architecture-overview]] to understand the system design. ## Database The database schema is documented in [[database-schema]]. All models use Prisma ORM. ## Next Steps 1. Read the [[architecture-overview]] 2. Explore the codebase 3. Check out the [[database-schema]] documentation`, summary: "Step-by-step guide to setting up the Mosaic Stack development environment", status: EntryStatus.PUBLISHED, visibility: Visibility.WORKSPACE, tags: ["development", "getting-started"], }, { slug: "database-schema", title: "Database Schema Documentation", content: `# Database Schema Documentation ## Overview Mosaic Stack uses PostgreSQL 17 with Prisma ORM. See [[architecture-overview]] for context. ## Key Conventions - All IDs are UUIDs - Timestamps use \`@db.Timestamptz\` (timezone-aware) - Soft deletes via status fields (e.g., ARCHIVED) - Relations enforce cascade deletes for data integrity ## Core Models ### Task Management - \`Task\` - Individual work items - \`Project\` - Task containers - \`Domain\` - High-level categorization ### Knowledge Module - \`KnowledgeEntry\` - Wiki pages - \`KnowledgeLink\` - Page connections - \`KnowledgeTag\` - Categorization - \`KnowledgeEmbedding\` - Semantic search (pgvector) ### Agent System - \`Agent\` - AI agent instances - \`AgentSession\` - Conversation sessions ## Migrations Migrations are managed via Prisma: \`\`\`bash # Create migration pnpm prisma migrate dev --name my_migration # Apply in production pnpm prisma migrate deploy \`\`\` For setup instructions, see [[development-setup]].`, summary: "Comprehensive documentation of the Mosaic Stack database schema and Prisma conventions", status: EntryStatus.PUBLISHED, visibility: Visibility.WORKSPACE, tags: ["architecture", "development"], }, { slug: "future-ideas", title: "Future Ideas and Roadmap", content: `# Future Ideas and Roadmap ## Planned Features - Real-time collaboration (CRDT) - Advanced graph visualizations - AI-powered summarization - Mobile app ## Research Areas - Vector search optimization - Knowledge graph algorithms - Agent memory systems This is a draft document. See [[architecture-overview]] for current state.`, summary: "Brainstorming document for future features and research directions", status: EntryStatus.DRAFT, visibility: Visibility.PRIVATE, tags: [], }, ]; // Create entries and track them for linking const createdEntries = new Map(); for (const entryData of entries) { const entry = await tx.knowledgeEntry.create({ data: { workspaceId: workspace.id, slug: entryData.slug, title: entryData.title, content: entryData.content, summary: entryData.summary, status: entryData.status, visibility: entryData.visibility, createdBy: user.id, updatedBy: user.id, }, }); createdEntries.set(entryData.slug, entry); // Create initial version await tx.knowledgeEntryVersion.create({ data: { entryId: entry.id, version: 1, title: entry.title, content: entry.content, summary: entry.summary, createdBy: user.id, changeNote: "Initial version", }, }); // Add tags for (const tagSlug of entryData.tags) { const tag = tags.find((t) => t.slug === tagSlug); if (tag) { await tx.knowledgeEntryTag.create({ data: { entryId: entry.id, tagId: tag.id, }, }); } } } console.log(`Created ${entries.length} knowledge entries`); // Create wiki-links between entries const links = [ { source: "welcome", target: "architecture-overview", text: "architecture-overview" }, { source: "welcome", target: "development-setup", text: "development-setup" }, { source: "welcome", target: "database-schema", text: "database-schema" }, { source: "architecture-overview", target: "development-setup", text: "development-setup" }, { source: "architecture-overview", target: "database-schema", text: "database-schema" }, { source: "development-setup", target: "architecture-overview", text: "architecture-overview", }, { source: "development-setup", target: "database-schema", text: "database-schema" }, { source: "database-schema", target: "architecture-overview", text: "architecture-overview" }, { source: "database-schema", target: "development-setup", text: "development-setup" }, { source: "future-ideas", target: "architecture-overview", text: "architecture-overview" }, ]; for (const link of links) { const sourceEntry = createdEntries.get(link.source); const targetEntry = createdEntries.get(link.target); if (sourceEntry && targetEntry) { await tx.knowledgeLink.create({ data: { sourceId: sourceEntry.id, targetId: targetEntry.id, linkText: link.text, }, }); } } console.log(`Created ${links.length} knowledge links`); }); console.log("Seeding completed successfully!"); } main() .catch((e) => { console.error("Error seeding database:", e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });