/** * Prisma seed script for RayLab Core - Phase 1 * Run with: npx ts-node prisma/seed.ts * Requires DATABASE_URL env var. */ import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); async function main() { console.log('Seeding default roles, permissions, and mappings...'); // Default roles const roles = [ { name: 'Owner', displayName: 'Owner', description: 'Platform owner with full access' }, { name: 'Employee', displayName: 'Employee', description: 'Employee role with internal permissions' }, { name: 'Family', displayName: 'Family', description: 'Family role with limited access' }, ]; for (const r of roles) { const code = r.name.toLowerCase(); await prisma.role.upsert({ where: { code }, update: { name: r.name, displayName: r.displayName, description: r.description }, create: { code, name: r.name, displayName: r.displayName, description: r.description }, }); } // Default permissions (resource.action lowercase as requested) const permissions = [ 'users.create', 'users.read', 'users.update', 'users.delete', 'roles.create', 'roles.read', 'roles.update', 'roles.delete', 'permissions.create', 'permissions.read', 'permissions.update', 'permissions.delete', 'audit.read', 'storage.read', 'storage.write', 'storage.delete', 'media.read', 'media.write', 'media.delete', 'scheduler.read', 'scheduler.manage', ]; for (const p of permissions) { // Use code as canonical identifier (resource.action). Use name == code for now. const code = p; await prisma.permission.upsert({ where: { code }, update: { name: p, description: null }, create: { code, name: p, description: null }, }); } // Assign permissions to roles (example mapping) const owner = await prisma.role.findUnique({ where: { code: 'owner' } }); const employee = await prisma.role.findUnique({ where: { code: 'employee' } }); const family = await prisma.role.findUnique({ where: { code: 'family' } }); if (!owner || !employee || !family) { throw new Error('Default roles missing after upsert'); } // Owner -> all permissions const allPerms = await prisma.permission.findMany(); for (const perm of allPerms) { await prisma.rolePermission.upsert({ where: { roleId_permissionId: { roleId: owner.id, permissionId: perm.id } }, update: {}, create: { roleId: owner.id, permissionId: perm.id }, }); } // Employee -> a subset const employeePerms = [ 'users.read', 'permissions.read', 'scheduler.read', 'scheduler.manage', 'media.read', 'media.write', ]; for (const p of employeePerms) { const perm = await prisma.permission.findUnique({ where: { code: p } }); if (perm) { await prisma.rolePermission.upsert({ where: { roleId_permissionId: { roleId: employee.id, permissionId: perm.id } }, update: {}, create: { roleId: employee.id, permissionId: perm.id }, }); } } // Family -> limited const familyPerms = [ 'media.read', 'media.write', 'media.delete', ]; for (const p of familyPerms) { const perm = await prisma.permission.findUnique({ where: { code: p } }); if (perm) { await prisma.rolePermission.upsert({ where: { roleId_permissionId: { roleId: family.id, permissionId: perm.id } }, update: {}, create: { roleId: family.id, permissionId: perm.id }, }); } } // Authentik group mappings const mappings = [ { authGroup: 'RL-Owner', roleName: 'Owner' }, { authGroup: 'RL-Employee', roleName: 'Employee' }, { authGroup: 'RL-Family', roleName: 'Family' }, ]; for (const m of mappings) { const role = await prisma.role.findUnique({ where: { name: m.roleName } }); if (!role) continue; // Ensure mapping exists (authGroup + roleId). AuthGroup can map to multiple roles. const existing = await prisma.authGroupRoleMapping.findFirst({ where: { authGroup: m.authGroup, roleId: role.id } }); if (!existing) { await prisma.authGroupRoleMapping.create({ data: { authGroup: m.authGroup, roleId: role.id } }); } } // Seed application catalog const apps = [ { code: 'core', name: 'Core', description: 'RayLab Core Dashboard', icon: 'mdi-apps', url: '/', applicationsClaim: 'core', displayOrder: 0 }, { code: 'warung', name: 'Warung', description: 'Warung app', icon: 'mdi-store', url: '/warung', applicationsClaim: 'warung', displayOrder: 10 }, { code: 'nextcloud', name: 'Nextcloud', description: 'Nextcloud', icon: 'mdi-cloud', url: 'https://nextcloud.example', applicationsClaim: 'nextcloud', displayOrder: 20 }, { code: 'jellyfin', name: 'Jellyfin', description: 'Jellyfin media server', icon: 'mdi-movie', url: 'https://jellyfin.example', applicationsClaim: 'jellyfin', displayOrder: 30 }, { code: 'forgejo', name: 'Forgejo', description: 'Forgejo git server', icon: 'mdi-source-repository', url: 'https://forgejo.example', applicationsClaim: 'forgejo', displayOrder: 40 }, { code: 'immich', name: 'Immich', description: 'Immich photo server', icon: 'mdi-image', url: 'https://immich.example', applicationsClaim: 'immich', displayOrder: 50 }, { code: 'grafana', name: 'Grafana', description: 'Grafana monitoring', icon: 'mdi-chart-line', url: 'https://grafana.example', applicationsClaim: 'grafana', displayOrder: 60 }, { code: 'vaultwarden', name: 'Vaultwarden', description: 'Vaultwarden password manager', icon: 'mdi-lock', url: 'https://vaultwarden.example', applicationsClaim: 'vaultwarden', displayOrder: 70 }, { code: 'homepage', name: 'Homepage', description: 'Homepage', icon: 'mdi-home', url: 'https://home.example', applicationsClaim: 'homepage', displayOrder: 80 }, ]; for (const a of apps) { await prisma.application.upsert({ where: { code: a.code }, update: { name: a.name, description: a.description, icon: a.icon, url: a.url, applicationsClaim: a.applicationsClaim, isActive: true, displayOrder: a.displayOrder }, create: { code: a.code, name: a.name, description: a.description, icon: a.icon, url: a.url, applicationsClaim: a.applicationsClaim, isActive: true, displayOrder: a.displayOrder }, }); } console.log('Seeding completed.'); } main() .catch(async (e) => { console.error(e); await prisma.$disconnect(); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });