feat(workspace): Add subdomain availability check (#8906)

Implemented a feature to check the availability of subdomains when
updating workspace settings. This includes a new mutation,
`isSubdomainAvailable`, to validate subdomain availability through
GraphQL. The frontend now verifies if a subdomain is available to
prevent duplicates during updates.

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Antoine Moreaux
2024-12-06 14:28:30 +01:00
committed by GitHub
parent 5c565345ae
commit 36fb14179b
9 changed files with 139 additions and 42 deletions

View File

@ -1,6 +1,6 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsBoolean, IsOptional, IsString } from 'class-validator';
import { IsBoolean, IsOptional, IsString, Matches } from 'class-validator';
@InputType()
export class UpdateWorkspaceInput {
@ -12,6 +12,7 @@ export class UpdateWorkspaceInput {
@Field({ nullable: true })
@IsString()
@IsOptional()
@Matches(/^[a-z0-9][a-z0-9-]{1,28}[a-z0-9]$/)
subdomain?: string;
@Field({ nullable: true })

View File

@ -18,6 +18,12 @@ import {
} from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceManagerService } from 'src/engine/workspace-manager/workspace-manager.service';
import { DEFAULT_FEATURE_FLAGS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/default-feature-flags';
import {
WorkspaceException,
WorkspaceExceptionCode,
} from 'src/engine/core-modules/workspace/workspace.exception';
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
import { ConflictError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
@Injectable()
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
@ -37,6 +43,35 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
super(workspaceRepository);
}
async updateWorkspaceById(payload: Partial<Workspace> & { id: string }) {
const workspace = await this.workspaceRepository.findOneBy({
id: payload.id,
});
workspaceValidator.assertIsExist(
workspace,
new WorkspaceException(
'Workspace not found',
WorkspaceExceptionCode.WORKSPACE_NOT_FOUND,
),
);
if (payload.subdomain && workspace.subdomain !== payload.subdomain) {
const subdomainAvailable = await this.isSubdomainAvailable(
payload.subdomain,
);
if (!subdomainAvailable) {
throw new ConflictError('Subdomain already taken');
}
}
return this.workspaceRepository.save({
...workspace,
...payload,
});
}
async activateWorkspace(user: User, data: ActivateWorkspaceInput) {
if (!data.displayName || !data.displayName.length) {
throw new BadRequestException("'displayName' not provided");
@ -159,4 +194,12 @@ export class WorkspaceService extends TypeOrmQueryService<Workspace> {
);
}
}
async isSubdomainAvailable(subdomain: string) {
const existingWorkspace = await this.workspaceRepository.findOne({
where: { subdomain: subdomain },
});
return !existingWorkspace;
}
}

View File

@ -91,7 +91,10 @@ export class WorkspaceResolver {
@Args('data') data: UpdateWorkspaceInput,
@AuthWorkspace() workspace: Workspace,
) {
return this.workspaceService.updateOne(workspace.id, data);
return this.workspaceService.updateWorkspaceById({
...data,
id: workspace.id,
});
}
@Mutation(() => String)