14240 extensibility ability to create multiple custom domains for each workspace 2 (#14307)
Adds a public-domain core-module. Reorganize custom-domain files properly
This commit is contained in:
+20
@@ -0,0 +1,20 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@ObjectType('PublicDomain')
|
||||
export class PublicDomainDTO {
|
||||
@IDField(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
domain: string;
|
||||
|
||||
@Field({ nullable: false })
|
||||
isValidated: boolean;
|
||||
|
||||
@Field()
|
||||
createdAt: Date;
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import { Field, ArgsType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
@ArgsType()
|
||||
export class PublicDomainInput {
|
||||
@Field(() => String)
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
domain: string;
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { Catch, ExceptionFilter } from '@nestjs/common';
|
||||
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
PublicDomainException,
|
||||
PublicDomainExceptionCode,
|
||||
} from 'src/engine/core-modules/public-domain/public-domain.exception';
|
||||
import { UserInputError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
|
||||
|
||||
@Catch(PublicDomainException)
|
||||
export class PublicDomainExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: PublicDomainException) {
|
||||
switch (exception.code) {
|
||||
case PublicDomainExceptionCode.PUBLIC_DOMAIN_ALREADY_REGISTERED:
|
||||
case PublicDomainExceptionCode.DOMAIN_ALREADY_REGISTERED_AS_CUSTOM_DOMAIN:
|
||||
throw new UserInputError(exception);
|
||||
default:
|
||||
assertUnreachable(exception.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Entity({ name: 'publicDomain', schema: 'core' })
|
||||
@ObjectType()
|
||||
export class PublicDomain {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt: Date;
|
||||
|
||||
@Column({ type: 'varchar', nullable: false, unique: true })
|
||||
domain: string;
|
||||
|
||||
@Column({ type: 'boolean', default: false, nullable: false })
|
||||
isValidated: boolean;
|
||||
|
||||
@Column({ nullable: false, type: 'uuid' })
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne(() => Workspace, (workspace) => workspace.publicDomains, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'workspaceId' })
|
||||
workspace: Relation<Workspace>;
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export class PublicDomainException extends CustomException<PublicDomainExceptionCode> {}
|
||||
|
||||
export enum PublicDomainExceptionCode {
|
||||
PUBLIC_DOMAIN_ALREADY_REGISTERED = 'PUBLIC_DOMAIN_ALREADY_REGISTERED',
|
||||
DOMAIN_ALREADY_REGISTERED_AS_CUSTOM_DOMAIN = 'DOMAIN_ALREADY_REGISTERED_AS_CUSTOM_DOMAIN',
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { PublicDomainService } from 'src/engine/core-modules/public-domain/public-domain.service';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import { PublicDomainResolver } from 'src/engine/core-modules/public-domain/public-domain.resolver';
|
||||
import { DnsManagerModule } from 'src/engine/core-modules/dns-manager/dns-manager.module';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([PublicDomain, Workspace]),
|
||||
DnsManagerModule,
|
||||
],
|
||||
providers: [PublicDomainService, PublicDomainResolver],
|
||||
})
|
||||
export class PublicDomainModule {}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
|
||||
|
||||
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { PublicDomainExceptionFilter } from 'src/engine/core-modules/public-domain/public-domain-exception-filter';
|
||||
import { PublicDomainService } from 'src/engine/core-modules/public-domain/public-domain.service';
|
||||
import { PublicDomainDTO } from 'src/engine/core-modules/public-domain/dtos/public-domain.dto';
|
||||
import { PublicDomainInput } from 'src/engine/core-modules/public-domain/dtos/public-domain.input';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@UseFilters(
|
||||
PublicDomainExceptionFilter,
|
||||
PreventNestToAutoLogGraphqlErrorsFilter,
|
||||
)
|
||||
@Resolver()
|
||||
export class PublicDomainResolver {
|
||||
constructor(private readonly publicDomainService: PublicDomainService) {}
|
||||
|
||||
@Mutation(() => PublicDomainDTO)
|
||||
async createPublicDomain(
|
||||
@Args() { domain }: PublicDomainInput,
|
||||
@AuthWorkspace() currentWorkspace: Workspace,
|
||||
): Promise<PublicDomainDTO> {
|
||||
return this.publicDomainService.createPublicDomain({
|
||||
domain,
|
||||
workspace: currentWorkspace,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
async deletePublicDomain(
|
||||
@Args() { domain }: PublicDomainInput,
|
||||
@AuthWorkspace() currentWorkspace: Workspace,
|
||||
): Promise<boolean> {
|
||||
await this.publicDomainService.deletePublicDomain({
|
||||
domain,
|
||||
workspace: currentWorkspace,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { PublicDomainDTO } from 'src/engine/core-modules/public-domain/dtos/public-domain.dto';
|
||||
import { PublicDomain } from 'src/engine/core-modules/public-domain/public-domain.entity';
|
||||
import {
|
||||
PublicDomainException,
|
||||
PublicDomainExceptionCode,
|
||||
} from 'src/engine/core-modules/public-domain/public-domain.exception';
|
||||
import { DnsManagerService } from 'src/engine/core-modules/dns-manager/services/dns-manager.service';
|
||||
|
||||
@Injectable()
|
||||
export class PublicDomainService {
|
||||
constructor(
|
||||
private readonly dnsManagerService: DnsManagerService,
|
||||
@InjectRepository(PublicDomain)
|
||||
private readonly publicDomainRepository: Repository<PublicDomain>,
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
) {}
|
||||
|
||||
async deletePublicDomain({
|
||||
domain,
|
||||
workspace,
|
||||
}: {
|
||||
domain: string;
|
||||
workspace: Workspace;
|
||||
}): Promise<void> {
|
||||
const formattedDomain = domain.trim().toLowerCase();
|
||||
|
||||
await this.dnsManagerService.deleteHostnameSilently(formattedDomain, {
|
||||
isPublicDomain: true,
|
||||
});
|
||||
|
||||
await this.publicDomainRepository.delete({
|
||||
domain: formattedDomain,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
async createPublicDomain({
|
||||
domain,
|
||||
workspace,
|
||||
}: {
|
||||
domain: string;
|
||||
workspace: Workspace;
|
||||
}): Promise<PublicDomainDTO> {
|
||||
const formattedDomain = domain.trim().toLowerCase();
|
||||
|
||||
if (
|
||||
await this.workspaceRepository.findOneBy({
|
||||
customDomain: formattedDomain,
|
||||
})
|
||||
) {
|
||||
throw new PublicDomainException(
|
||||
'Domain already used for workspace custom domain',
|
||||
PublicDomainExceptionCode.DOMAIN_ALREADY_REGISTERED_AS_CUSTOM_DOMAIN,
|
||||
{
|
||||
userFriendlyMessage: t`Domain already used for workspace custom domain`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
await this.publicDomainRepository.findOneBy({
|
||||
domain: formattedDomain,
|
||||
workspaceId: workspace.id,
|
||||
})
|
||||
) {
|
||||
throw new PublicDomainException(
|
||||
'Public domain already registered',
|
||||
PublicDomainExceptionCode.PUBLIC_DOMAIN_ALREADY_REGISTERED,
|
||||
{
|
||||
userFriendlyMessage: t`Public domain already registered`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
const publicDomain = this.publicDomainRepository.create({
|
||||
domain: formattedDomain,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
await this.dnsManagerService.registerHostname(formattedDomain, {
|
||||
isPublicDomain: true,
|
||||
});
|
||||
|
||||
try {
|
||||
await this.publicDomainRepository.insert(publicDomain);
|
||||
} catch (error) {
|
||||
await this.dnsManagerService.deleteHostnameSilently(formattedDomain, {
|
||||
isPublicDomain: true,
|
||||
});
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return publicDomain;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user