From ab9498b3de00929fb9fb6323ef40c6aba5a9bb66 Mon Sep 17 00:00:00 2001 From: Antoine Moreaux Date: Tue, 18 Mar 2025 14:06:24 +0100 Subject: [PATCH] feat(workspace): delete custom domain on hard workspace delete (#10975) Add logic to remove a workspace's custom domain during hard deletion. Includes tests to verify behavior for both hard and soft deletion cases. Fix #10351 --- .../__tests__/workspace.service.spec.ts | 50 +++++++++++++++++++ .../workspace/services/workspace.service.ts | 7 +++ 2 files changed, 57 insertions(+) diff --git a/packages/twenty-server/src/engine/core-modules/workspace/__tests__/workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/workspace/__tests__/workspace.service.spec.ts index 54dfa4285..7cfbc3cb3 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/__tests__/workspace.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/__tests__/workspace.service.spec.ts @@ -33,6 +33,7 @@ describe('WorkspaceService', () => { let workspaceRepository: Repository; let workspaceCacheStorageService: WorkspaceCacheStorageService; let messageQueueService: MessageQueueService; + let customDomainService: CustomDomainService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -112,6 +113,8 @@ describe('WorkspaceService', () => { messageQueueService = module.get( getQueueToken(MessageQueue.deleteCascadeQueue), ); + customDomainService = module.get(CustomDomainService); + customDomainService.deleteCustomHostnameByHostnameSilently = jest.fn(); }); afterEach(() => { @@ -224,5 +227,52 @@ describe('WorkspaceService', () => { }); expect(workspaceRepository.delete).not.toHaveBeenCalled(); }); + + it('should delete the custom domain when hard deleting a workspace with a custom domain', async () => { + const customDomain = 'custom.example.com'; + const mockWorkspace = { + id: 'workspace-id', + metadataVersion: 0, + customDomain, + } as Workspace; + + jest + .spyOn(workspaceRepository, 'findOne') + .mockResolvedValue(mockWorkspace); + jest.spyOn(userWorkspaceRepository, 'find').mockResolvedValue([]); + jest + .spyOn(service, 'deleteMetadataSchemaCacheAndUserWorkspace') + .mockResolvedValue({} as Workspace); + + await service.deleteWorkspace(mockWorkspace.id, false); + + expect( + customDomainService.deleteCustomHostnameByHostnameSilently, + ).toHaveBeenCalledWith(customDomain); + expect(workspaceRepository.delete).toHaveBeenCalledWith(mockWorkspace.id); + }); + + it('should not delete the custom domain when soft deleting a workspace with a custom domain', async () => { + const customDomain = 'custom.example.com'; + const mockWorkspace = { + id: 'workspace-id', + metadataVersion: 0, + customDomain, + } as Workspace; + + jest + .spyOn(workspaceRepository, 'findOne') + .mockResolvedValue(mockWorkspace); + jest.spyOn(userWorkspaceRepository, 'find').mockResolvedValue([]); + + await service.deleteWorkspace(mockWorkspace.id, true); + + expect( + customDomainService.deleteCustomHostnameByHostnameSilently, + ).not.toHaveBeenCalled(); + expect(workspaceRepository.softDelete).toHaveBeenCalledWith({ + id: mockWorkspace.id, + }); + }); }); }); diff --git a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts index f96e6f553..3d39bd686 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/services/workspace.service.ts @@ -346,6 +346,13 @@ export class WorkspaceService extends TypeOrmQueryService { FileWorkspaceFolderDeletionJob.name, { workspaceId: id }, ); + + if (workspace.customDomain) { + await this.customDomainService.deleteCustomHostnameByHostnameSilently( + workspace.customDomain, + ); + } + await this.workspaceRepository.delete(id); this.logger.log(`workspace ${id} hard deleted`);