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', themeMode: 'dark', primaryHue: 240, primarySaturation: 80, backgroundType: 'aurora', locale: 'en' } }); 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', themeMode: 'light', primaryHue: 150, primarySaturation: 60, backgroundType: 'mesh', locale: 'ru' } }); 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 }, { name: 'Wiki.js', url: 'http://wiki.local:3000', icon: 'http://wiki.local:3000/favicon.ico', iconType: 'url', description: 'Quick-added wiki service (demonstrates favicon URL icon)', category: 'Productivity', tags: 'wiki,docs,knowledge', 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', 'widget-bookmark-docs', 'widget-note-welcome', 'widget-embed-grafana', 'widget-status-infra' ]; 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 }) } }); // --- Bookmark widget --- await prisma.widget.create({ data: { id: 'widget-bookmark-docs', sectionId: mediaSection.id, type: 'bookmark', order: 1, config: JSON.stringify({ url: 'https://docs.selfhosted.example.com', label: 'Self-Hosted Docs', icon: 'book-open', description: 'Documentation for all self-hosted services' }) } }); // --- Note widget --- await prisma.widget.create({ data: { id: 'widget-note-welcome', sectionId: mediaSection.id, type: 'note', order: 2, config: JSON.stringify({ content: '# Welcome\n\nThis is your **home dashboard**. Use sections to organize apps, bookmarks, notes, and more.\n\n- Drag to reorder\n- Click to launch\n- Edit to customize', format: 'markdown' }) } }); // --- Embed widget --- await prisma.widget.create({ data: { id: 'widget-embed-grafana', sectionId: infraSection.id, type: 'embed', order: 5, config: JSON.stringify({ url: 'http://grafana.local:3000/d/server-stats/overview?orgId=1&kiosk', height: 400 }) } }); // --- Status widget --- await prisma.widget.create({ data: { id: 'widget-status-infra', sectionId: networkSection.id, type: 'status', order: 1, config: JSON.stringify({ appIds: [createdApps[4].id, createdApps[5].id, createdApps[6].id], label: 'Infrastructure Status' }) } }); console.log(' Created widgets for all apps (including bookmark, note, embed, status)'); // --- Second Board with permissions --- const teamBoard = await prisma.board.upsert({ where: { id: 'team-board' }, update: {}, create: { id: 'team-board', name: 'Team Board', icon: 'users', description: 'A board with permission controls for the team', isDefault: false, isGuestAccessible: false, createdById: admin.id } }); console.log(' Created board:', teamBoard.name); // Grant 'view' permission to the regular user on the team board await prisma.permission.upsert({ where: { entityType_entityId_targetType_targetId: { entityType: 'board', entityId: teamBoard.id, targetType: 'user', targetId: regularUser.id } }, update: { level: 'view' }, create: { entityType: 'board', entityId: teamBoard.id, targetType: 'user', targetId: regularUser.id, level: 'view' } }); // Grant 'edit' permission to the 'user' group on the team board await prisma.permission.upsert({ where: { entityType_entityId_targetType_targetId: { entityType: 'board', entityId: teamBoard.id, targetType: 'group', targetId: userGroup.id } }, update: { level: 'edit' }, create: { entityType: 'board', entityId: teamBoard.id, targetType: 'group', targetId: userGroup.id, level: 'edit' } }); console.log(' Set permissions on team board'); console.log('Seeding complete!'); } main() .catch((e) => { console.error('Seed error:', e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });