feat(*): allow to select auth providers + add multiworkspace with subdomain management (#8656)
## Summary Add support for multi-workspace feature and adjust configurations and states accordingly. - Introduced new state isMultiWorkspaceEnabledState. - Updated ClientConfigProviderEffect component to handle multi-workspace. - Modified GraphQL schema and queries to include multi-workspace related configurations. - Adjusted server environment variables and their respective documentation to support multi-workspace toggle. - Updated server-side logic to handle new multi-workspace configurations and conditions.
This commit is contained in:
@ -0,0 +1,123 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository, In } from 'typeorm';
|
||||
|
||||
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { BaseCommandOptions } from 'src/database/commands/base.command';
|
||||
|
||||
// For DX only
|
||||
type WorkspaceId = string;
|
||||
|
||||
type Subdomain = string;
|
||||
|
||||
@Command({
|
||||
name: 'feat-0.34:add-subdomain-to-workspace',
|
||||
description: 'Add a default subdomain to each workspace',
|
||||
})
|
||||
export class GenerateDefaultSubdomainCommand extends ActiveWorkspacesCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
|
||||
private generatePayloadForQuery({
|
||||
id,
|
||||
subdomain,
|
||||
domainName,
|
||||
displayName,
|
||||
}: Workspace) {
|
||||
const result = { id, subdomain };
|
||||
|
||||
if (domainName) {
|
||||
const subdomain = domainName.split('.')[0];
|
||||
|
||||
if (subdomain.length > 0) {
|
||||
result.subdomain = subdomain;
|
||||
}
|
||||
}
|
||||
|
||||
if (!domainName && displayName) {
|
||||
const displayNameWords = displayName.match(/(\w| |\d)+/);
|
||||
|
||||
if (displayNameWords) {
|
||||
result.subdomain = displayNameWords
|
||||
.join('-')
|
||||
.replace(/ /g, '')
|
||||
.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private groupBySubdomainName(
|
||||
acc: Record<Subdomain, Array<WorkspaceId>>,
|
||||
workspace: Workspace,
|
||||
) {
|
||||
const payload = this.generatePayloadForQuery(workspace);
|
||||
|
||||
acc[payload.subdomain] = acc[payload.subdomain]
|
||||
? acc[payload.subdomain].concat([payload.id])
|
||||
: [payload.id];
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
private async deduplicateAndSave(
|
||||
subdomain: Subdomain,
|
||||
workspaceIds: Array<WorkspaceId>,
|
||||
options: BaseCommandOptions,
|
||||
) {
|
||||
for (const [index, workspaceId] of workspaceIds.entries()) {
|
||||
const subdomainDeduplicated =
|
||||
index === 0 ? subdomain : `${subdomain}-${index}`;
|
||||
|
||||
this.logger.log(
|
||||
`Updating workspace ${workspaceId} with subdomain ${subdomainDeduplicated}`,
|
||||
);
|
||||
|
||||
if (!options.dryRun) {
|
||||
await this.workspaceRepository.update(workspaceId, {
|
||||
subdomain: subdomainDeduplicated,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async executeActiveWorkspacesCommand(
|
||||
passedParam: string[],
|
||||
options: BaseCommandOptions,
|
||||
activeWorkspaceIds: string[],
|
||||
): Promise<void> {
|
||||
const workspaces = await this.workspaceRepository.find(
|
||||
activeWorkspaceIds.length > 0
|
||||
? {
|
||||
where: {
|
||||
id: In(activeWorkspaceIds),
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
);
|
||||
|
||||
if (workspaces.length === 0) {
|
||||
this.logger.log('No workspaces found');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceBySubdomain = Object.entries(
|
||||
workspaces.reduce(
|
||||
(acc, workspace) => this.groupBySubdomainName(acc, workspace),
|
||||
{} as ReturnType<typeof this.groupBySubdomainName>,
|
||||
),
|
||||
);
|
||||
|
||||
for (const [subdomain, workspaceIds] of workspaceBySubdomain) {
|
||||
await this.deduplicateAndSave(subdomain, workspaceIds, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveWorkspacesCommandRunner } from 'src/database/commands/active-workspaces.command';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { GenerateDefaultSubdomainCommand } from 'src/database/commands/upgrade-version/0-34/0-34-generate-subdomain.command';
|
||||
|
||||
interface UpdateTo0_34CommandOptions {
|
||||
workspaceId?: string;
|
||||
}
|
||||
|
||||
@Command({
|
||||
name: 'upgrade-0.34',
|
||||
description: 'Upgrade to 0.34',
|
||||
})
|
||||
export class UpgradeTo0_34Command extends ActiveWorkspacesCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(Workspace, 'core')
|
||||
protected readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly generateDefaultSubdomainCommand: GenerateDefaultSubdomainCommand,
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
|
||||
async executeActiveWorkspacesCommand(
|
||||
passedParam: string[],
|
||||
options: UpdateTo0_34CommandOptions,
|
||||
workspaceIds: string[],
|
||||
): Promise<void> {
|
||||
await this.generateDefaultSubdomainCommand.executeActiveWorkspacesCommand(
|
||||
passedParam,
|
||||
options,
|
||||
workspaceIds,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { SearchModule } from 'src/engine/metadata-modules/search/search.module';
|
||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
|
||||
import { WorkspaceSyncMetadataCommandsModule } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/workspace-sync-metadata-commands.module';
|
||||
import { UpgradeTo0_34Command } from 'src/database/commands/upgrade-version/0-34/0-34-upgrade-version.command';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Workspace], 'core'),
|
||||
TypeOrmModule.forFeature(
|
||||
[ObjectMetadataEntity, FieldMetadataEntity],
|
||||
'metadata',
|
||||
),
|
||||
WorkspaceSyncMetadataCommandsModule,
|
||||
SearchModule,
|
||||
WorkspaceMigrationRunnerModule,
|
||||
],
|
||||
providers: [UpgradeTo0_34Command],
|
||||
})
|
||||
export class UpgradeTo0_33CommandModule {}
|
||||
@ -23,6 +23,7 @@ export const seedWorkspaces = async (
|
||||
| 'domainName'
|
||||
| 'inviteHash'
|
||||
| 'logo'
|
||||
| 'subdomain'
|
||||
| 'activationStatus'
|
||||
>;
|
||||
} = {
|
||||
@ -30,6 +31,7 @@ export const seedWorkspaces = async (
|
||||
id: workspaceId,
|
||||
displayName: 'Apple',
|
||||
domainName: 'apple.dev',
|
||||
subdomain: 'apple',
|
||||
inviteHash: 'apple.dev-invite-hash',
|
||||
logo: 'https://twentyhq.github.io/placeholder-images/workspaces/apple-logo.png',
|
||||
activationStatus: WorkspaceActivationStatus.ACTIVE,
|
||||
@ -38,6 +40,7 @@ export const seedWorkspaces = async (
|
||||
id: workspaceId,
|
||||
displayName: 'Acme',
|
||||
domainName: 'acme.dev',
|
||||
subdomain: 'acme',
|
||||
inviteHash: 'acme.dev-invite-hash',
|
||||
logo: 'https://logos-world.net/wp-content/uploads/2022/05/Acme-Logo-700x394.png',
|
||||
activationStatus: WorkspaceActivationStatus.ACTIVE,
|
||||
@ -51,6 +54,7 @@ export const seedWorkspaces = async (
|
||||
'id',
|
||||
'displayName',
|
||||
'domainName',
|
||||
'subdomain',
|
||||
'inviteHash',
|
||||
'logo',
|
||||
'activationStatus',
|
||||
|
||||
@ -0,0 +1,26 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddSubdomainToWorkspace1730137590546
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddSubdomainToWorkspace1730137590546';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD "subdomain" varchar NULL`,
|
||||
);
|
||||
await queryRunner.query(`UPDATE "core"."workspace" SET "subdomain" = "id"`);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ALTER COLUMN "subdomain" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX workspace_subdomain_unique_index ON "core"."workspace" (subdomain)`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP COLUMN "subdomain"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddAuthProvidersColumnsToWorkspace1730298416367
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddAuthProvidersColumnsToWorkspace1730298416367';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD "isMicrosoftAuthEnabled" BOOLEAN DEFAULT false`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD "isGoogleAuthEnabled" BOOLEAN DEFAULT true`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" ADD "isPasswordAuthEnabled" BOOLEAN DEFAULT true`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP COLUMN "isMicrosoftAuthEnabled"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP COLUMN "isGoogleAuthEnabled"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."workspace" DROP COLUMN "isPasswordAuthEnabled"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user