feat(app): wire dashboard administration flows

This commit is contained in:
2026-05-07 15:35:37 +00:00
parent e9eb9b4172
commit 91f06ccae1
91 changed files with 4087 additions and 5863 deletions
+312
View File
@@ -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();
}
}
}