feat(app): wire dashboard administration flows
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user