import { PrismaClient } from '@prisma/client'; import bcrypt from 'bcryptjs'; const prisma = new PrismaClient(); async function main() { console.log('Seeding database...'); // --- System Settings --- const settings = await prisma.systemSettings.upsert({ where: { id: 'singleton' }, update: {}, create: { id: 'singleton', authMode: 'local', registrationEnabled: true, defaultTheme: 'dark', defaultPrimaryColor: '#6366f1', healthcheckDefaults: JSON.stringify({ interval: 300, timeout: 5000, method: 'GET', expectedStatus: 200 }) } }); console.log(' Created system settings:', settings.id); // --- Admin User --- const adminPassword = await bcrypt.hash('admin123', 12); const admin = await prisma.user.upsert({ where: { email: 'admin@launcher.local' }, update: {}, create: { email: 'admin@launcher.local', password: adminPassword, displayName: 'Administrator', role: 'admin', authProvider: 'local' } }); console.log(' Created admin user:', admin.email); // --- Regular User --- const userPassword = await bcrypt.hash('user123', 12); const regularUser = await prisma.user.upsert({ where: { email: 'user@launcher.local' }, update: {}, create: { email: 'user@launcher.local', password: userPassword, displayName: 'Demo User', role: 'user', authProvider: 'local' } }); console.log(' Created regular user:', regularUser.email); // --- Groups --- const adminGroup = await prisma.group.upsert({ where: { name: 'admin' }, update: {}, create: { name: 'admin', description: 'Administrators with full system access', isDefault: false } }); console.log(' Created group:', adminGroup.name); const userGroup = await prisma.group.upsert({ where: { name: 'user' }, update: {}, create: { name: 'user', description: 'Default group for all registered users', isDefault: true } }); console.log(' Created group:', userGroup.name); // --- User-Group memberships --- await prisma.userGroup.upsert({ where: { userId_groupId: { userId: admin.id, groupId: adminGroup.id } }, update: {}, create: { userId: admin.id, groupId: adminGroup.id } }); await prisma.userGroup.upsert({ where: { userId_groupId: { userId: admin.id, groupId: userGroup.id } }, update: {}, create: { userId: admin.id, groupId: userGroup.id } }); await prisma.userGroup.upsert({ where: { userId_groupId: { userId: regularUser.id, groupId: userGroup.id } }, update: {}, create: { userId: regularUser.id, groupId: userGroup.id } }); console.log(' Added users to groups'); // --- Sample Apps --- const appDefinitions = [ { name: 'Plex', url: 'http://plex.local:32400', icon: 'plex', iconType: 'simple', description: 'Media server for streaming movies, TV shows, and music', category: 'Media', tags: 'media,streaming,movies,tv', healthcheckEnabled: true }, { name: 'Nextcloud', url: 'http://nextcloud.local', icon: 'nextcloud', iconType: 'simple', description: 'Self-hosted file sync, sharing, and collaboration platform', category: 'Productivity', tags: 'files,sync,cloud,office', healthcheckEnabled: true }, { name: 'Gitea', url: 'http://gitea.local:3000', icon: 'gitea', iconType: 'simple', description: 'Lightweight self-hosted Git service', category: 'Development', tags: 'git,code,development,ci', healthcheckEnabled: true }, { name: 'Home Assistant', url: 'http://homeassistant.local:8123', icon: 'homeassistant', iconType: 'simple', description: 'Open-source home automation platform', category: 'Home Automation', tags: 'home,automation,iot,smart-home', healthcheckEnabled: true }, { name: 'Grafana', url: 'http://grafana.local:3000', icon: 'grafana', iconType: 'simple', description: 'Analytics and monitoring dashboards', category: 'Monitoring', tags: 'monitoring,analytics,dashboards,metrics', healthcheckEnabled: true }, { name: 'Portainer', url: 'http://portainer.local:9000', icon: 'portainer', iconType: 'simple', description: 'Container management UI for Docker and Kubernetes', category: 'Infrastructure', tags: 'docker,containers,kubernetes,management', healthcheckEnabled: true }, { name: 'Pi-hole', url: 'http://pihole.local/admin', icon: 'pihole', iconType: 'simple', description: 'Network-wide ad blocking DNS sinkhole', category: 'Network', tags: 'dns,adblock,network,privacy', healthcheckEnabled: true } ]; // Create apps using create (delete existing first for idempotency) const createdApps = []; for (const appData of appDefinitions) { // Delete existing app with same name if present (for re-seeding) await prisma.app.deleteMany({ where: { name: appData.name } }); const app = await prisma.app.create({ data: { ...appData, createdById: admin.id } }); createdApps.push(app); console.log(' Created app:', app.name); } // --- Default Board --- const board = await prisma.board.upsert({ where: { id: 'default-board' }, update: {}, create: { id: 'default-board', name: 'Dashboard', icon: 'layout-dashboard', description: 'Default application dashboard', isDefault: true, isGuestAccessible: true, createdById: admin.id } }); console.log(' Created board:', board.name); // --- Sections --- const mediaSection = await prisma.section.upsert({ where: { id: 'section-media' }, update: {}, create: { id: 'section-media', boardId: board.id, title: 'Media & Entertainment', icon: 'tv', order: 0, isExpandedByDefault: true } }); console.log(' Created section:', mediaSection.title); const infraSection = await prisma.section.upsert({ where: { id: 'section-infra' }, update: {}, create: { id: 'section-infra', boardId: board.id, title: 'Infrastructure & Tools', icon: 'server', order: 1, isExpandedByDefault: true } }); console.log(' Created section:', infraSection.title); const networkSection = await prisma.section.upsert({ where: { id: 'section-network' }, update: {}, create: { id: 'section-network', boardId: board.id, title: 'Network & Security', icon: 'shield', order: 2, isExpandedByDefault: true } }); console.log(' Created section:', networkSection.title); // --- Widgets --- // Delete existing seed widgets for idempotency const seedWidgetIds = [ 'widget-plex', 'widget-nextcloud', 'widget-gitea', 'widget-homeassistant', 'widget-grafana', 'widget-portainer', 'widget-pihole' ]; await prisma.widget.deleteMany({ where: { id: { in: seedWidgetIds } } }); // Media section widgets await prisma.widget.create({ data: { id: 'widget-plex', sectionId: mediaSection.id, type: 'app', order: 0, appId: createdApps[0].id, config: JSON.stringify({ showStatus: true, openInNewTab: true }) } }); // Infrastructure section widgets await prisma.widget.create({ data: { id: 'widget-nextcloud', sectionId: infraSection.id, type: 'app', order: 0, appId: createdApps[1].id, config: JSON.stringify({ showStatus: true, openInNewTab: true }) } }); await prisma.widget.create({ data: { id: 'widget-gitea', sectionId: infraSection.id, type: 'app', order: 1, appId: createdApps[2].id, config: JSON.stringify({ showStatus: true, openInNewTab: true }) } }); await prisma.widget.create({ data: { id: 'widget-homeassistant', sectionId: infraSection.id, type: 'app', order: 2, appId: createdApps[3].id, config: JSON.stringify({ showStatus: true, openInNewTab: true }) } }); await prisma.widget.create({ data: { id: 'widget-grafana', sectionId: infraSection.id, type: 'app', order: 3, appId: createdApps[4].id, config: JSON.stringify({ showStatus: true, openInNewTab: true }) } }); await prisma.widget.create({ data: { id: 'widget-portainer', sectionId: infraSection.id, type: 'app', order: 4, appId: createdApps[5].id, config: JSON.stringify({ showStatus: true, openInNewTab: true }) } }); // Network section widgets await prisma.widget.create({ data: { id: 'widget-pihole', sectionId: networkSection.id, type: 'app', order: 0, appId: createdApps[6].id, config: JSON.stringify({ showStatus: true, openInNewTab: true }) } }); console.log(' Created widgets for all apps'); console.log('Seeding complete!'); } main() .catch((e) => { console.error('Seed error:', e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });