import * as plugins from './plugins.js'; import { App } from '../ts/reception/classes.app.js'; import { Organization } from '../ts/reception/classes.organization.js'; import { Role } from '../ts/reception/classes.role.js'; import { User } from '../ts/reception/classes.user.js'; export type TSeedScenario = 'admin' | 'workspace' | 'globalApps'; export interface ISeedOptions { scenario: TSeedScenario; adminEmail: string; adminPassword: string; adminName: string; organizationName: string; organizationSlug: string; } export class SeedRunner { public qenv = new plugins.qenv.Qenv('./', './.nogit', false); public smartdataDb: plugins.smartdata.SmartdataDb; public CUser = plugins.smartdata.setDefaultManagerForDoc(this, User); public COrganization = plugins.smartdata.setDefaultManagerForDoc(this, Organization); public CRole = plugins.smartdata.setDefaultManagerForDoc(this, Role); public CApp = plugins.smartdata.setDefaultManagerForDoc(this, App); public get db() { return this.smartdataDb; } public async start() { const mongoDbUrl = await this.qenv.getEnvVarOnDemandStrict('MONGODB_URL'); this.smartdataDb = new plugins.smartdata.SmartdataDb({ mongoDbUrl }); await this.smartdataDb.init(); } public async stop() { if (this.smartdataDb) { await this.smartdataDb.close(); } } public async seed(optionsArg: ISeedOptions) { if (optionsArg.scenario === 'globalApps') { await this.seedGlobalApps(); return; } const adminUser = await this.seedAdminUser(optionsArg); const organization = await this.seedOrganization(optionsArg, adminUser.id); await this.seedOwnerRole(adminUser.id, organization.id); await this.seedGlobalApps(); if (optionsArg.scenario === 'workspace') { await this.seedWorkspaceUsers(organization.id); } } private async seedAdminUser(optionsArg: ISeedOptions) { let adminUser = await this.CUser.getInstance({ data: { email: optionsArg.adminEmail, }, }); if (!adminUser) { adminUser = await this.CUser.createNewUserForUserData({ name: optionsArg.adminName, username: optionsArg.adminEmail, email: optionsArg.adminEmail, password: optionsArg.adminPassword, status: 'active', connectedOrgs: [], }); } adminUser.data.name = optionsArg.adminName; adminUser.data.username = optionsArg.adminEmail; adminUser.data.email = optionsArg.adminEmail; adminUser.data.status = 'active'; adminUser.data.isGlobalAdmin = true; adminUser.data.passwordHash = await this.CUser.hashPassword(optionsArg.adminPassword); await adminUser.save(); return adminUser; } private async seedOrganization(optionsArg: ISeedOptions, adminUserIdArg: string) { let organization = await this.COrganization.getInstance({ data: { slug: optionsArg.organizationSlug, }, }); if (!organization) { organization = await this.COrganization.createNewOrganizationForUser( this as any, adminUserIdArg, optionsArg.organizationName, optionsArg.organizationSlug, ); } organization.data.name = optionsArg.organizationName; organization.data.slug = optionsArg.organizationSlug; organization.data.roleIds = organization.data.roleIds || []; this.seedDefaultOrgRoleDefinitions(organization); await organization.save(); const adminUser = await this.CUser.getInstance({ id: adminUserIdArg }); if (adminUser && !adminUser.data.connectedOrgs.includes(organization.id)) { adminUser.data.connectedOrgs.push(organization.id); await adminUser.save(); } return organization; } private seedDefaultOrgRoleDefinitions(organizationArg: Organization) { const now = Date.now(); const defaultRoleDefinitions = [ { key: 'finance', name: 'Finance', description: 'Billing, invoice, and procurement access.' }, { key: 'engineering', name: 'Engineering', description: 'Developer and infrastructure access.' }, { key: 'support', name: 'Support', description: 'Customer and incident support access.' }, { key: 'contractor', name: 'Contractor', description: 'Limited temporary external access.' }, ]; const roleDefinitions = organizationArg.data.roleDefinitions || []; for (const defaultRoleDefinition of defaultRoleDefinitions) { const existingRoleDefinition = roleDefinitions.find((roleDefinitionArg) => roleDefinitionArg.key === defaultRoleDefinition.key); if (existingRoleDefinition) { existingRoleDefinition.name = defaultRoleDefinition.name; existingRoleDefinition.description = defaultRoleDefinition.description; existingRoleDefinition.updatedAt = now; } else { roleDefinitions.push({ ...defaultRoleDefinition, createdAt: now, updatedAt: now, }); } } organizationArg.data.roleDefinitions = roleDefinitions.sort((leftArg, rightArg) => leftArg.name.localeCompare(rightArg.name)); } private async seedOwnerRole(userIdArg: string, organizationIdArg: string) { let role = await this.CRole.getInstance({ data: { userId: userIdArg, organizationId: organizationIdArg, }, }); if (!role) { role = new this.CRole(); role.id = plugins.smartunique.shortId(); role.data = { userId: userIdArg, organizationId: organizationIdArg, roles: ['owner', 'admin'], }; } else { role.data.roles = [...new Set([...role.data.roles, 'owner', 'admin'])]; } await role.save(); const organization = await this.COrganization.getInstance({ id: organizationIdArg }); if (organization && !organization.data.roleIds.includes(role.id)) { organization.data.roleIds.push(role.id); await organization.save(); } } private async seedWorkspaceUsers(organizationIdArg: string) { const users = [ { email: 'alex@idp.global', name: 'Alex Mercer', roles: ['admin'], }, { email: 'jane@idp.global', name: 'Jane Doe', roles: ['editor'], }, { email: 'sam@idp.global', name: 'Sam Chen', roles: ['viewer'], }, ]; for (const userData of users) { let user = await this.CUser.getInstance({ data: { email: userData.email, }, }); if (!user) { user = await this.CUser.createNewUserForUserData({ name: userData.name, username: userData.email, email: userData.email, password: 'idp.global', status: 'active', connectedOrgs: [], }); } user.data.name = userData.name; user.data.username = userData.email; user.data.status = 'active'; user.data.passwordHash = await this.CUser.hashPassword('idp.global'); if (!user.data.connectedOrgs.includes(organizationIdArg)) { user.data.connectedOrgs.push(organizationIdArg); } await user.save(); let role = await this.CRole.getInstance({ data: { userId: user.id, organizationId: organizationIdArg, }, }); if (!role) { role = new this.CRole(); role.id = plugins.smartunique.shortId(); } role.data = { userId: user.id, organizationId: organizationIdArg, roles: userData.roles, }; await role.save(); const organization = await this.COrganization.getInstance({ id: organizationIdArg }); if (organization && !organization.data.roleIds.includes(role.id)) { organization.data.roleIds.push(role.id); await organization.save(); } } } private async seedGlobalApps() { const defaultGlobalApps: Array<{ id: string; name: string; description: string; logoUrl: string; appUrl: string; clientId: string; redirectUris: string[]; category: string; }> = [ { id: 'app-foss-global', name: 'foss.global', description: 'Open Source Package Registry and Collaboration Platform', logoUrl: 'https://foss.global/assets/logo.png', appUrl: 'https://foss.global', clientId: 'foss-global-client', redirectUris: ['https://foss.global/auth/callback'], category: 'Development', }, { id: 'app-task-vc', name: 'task.vc', description: 'Task Management and Project Collaboration', logoUrl: 'https://task.vc/assets/logo.png', appUrl: 'https://task.vc', clientId: 'task-vc-client', redirectUris: ['https://task.vc/auth/callback'], category: 'Productivity', }, { id: 'app-hetzner-cloud', name: 'Hetzner Cloud', description: 'Cloud infrastructure console access', logoUrl: 'https://www.hetzner.com/favicon.ico', appUrl: 'https://console.hetzner.cloud', clientId: 'hetzner-cloud-client', redirectUris: ['https://console.hetzner.cloud/oauth/callback'], category: 'Infrastructure', }, ]; for (const appData of defaultGlobalApps) { let app = await this.CApp.getInstance({ id: appData.id }); if (!app) { app = new this.CApp(); app.id = appData.id; app.type = 'global'; } app.data = { name: appData.name, description: appData.description, logoUrl: appData.logoUrl, appUrl: appData.appUrl, oauthCredentials: { clientId: appData.clientId, clientSecretHash: '', redirectUris: appData.redirectUris, allowedScopes: ['openid', 'profile', 'email', 'organizations'], grantTypes: ['authorization_code', 'refresh_token'], }, isActive: true, category: appData.category, createdAt: Date.now(), createdByUserId: 'seed', }; await app.save(); } } }