From 983c40485b1cf9d2652078bd06d9c5605906b3b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Sat, 9 Aug 2025 00:44:17 +0200 Subject: [PATCH] Add destroy for core view resolvers (#13745) Add destroy for core view resolvers --------- Co-authored-by: Charles Bochet --- .../view/controllers/view-field.controller.ts | 2 + .../view-filter-group.controller.ts | 2 + .../controllers/view-filter.controller.ts | 2 + .../view/controllers/view-group.controller.ts | 2 + .../view/controllers/view-sort.controller.ts | 2 + .../view/controllers/view.controller.ts | 2 + .../view/resolvers/view-field.resolver.ts | 13 ++++++ .../resolvers/view-filter-group.resolver.ts | 13 ++++++ .../view/resolvers/view-filter.resolver.ts | 13 ++++++ .../view/resolvers/view-group.resolver.ts | 13 ++++++ .../view/resolvers/view-sort.resolver.ts | 13 ++++++ .../view/resolvers/view.resolver.ts | 10 +++++ .../services/tests/view-field.service.spec.ts | 16 ++++++++ .../tests/view-filter-group.service.spec.ts | 24 +++++++++++ .../tests/view-filter.service.spec.ts | 19 +++++++++ .../services/tests/view-group.service.spec.ts | 17 ++++++++ .../services/tests/view-sort.service.spec.ts | 17 ++++++++ .../view/services/tests/view.service.spec.ts | 17 ++++++++ .../view/services/view-field.service.ts | 18 ++++++++ .../services/view-filter-group.service.ts | 18 ++++++++ .../view/services/view-filter.service.ts | 18 ++++++++ .../view/services/view-group.service.ts | 18 ++++++++ .../view/services/view-sort.service.ts | 18 ++++++++ .../view/services/view.service.ts | 18 ++++++++ .../view-field-resolver.integration-spec.ts | 36 ++++++++++++++++ ...-filter-group-resolver.integration-spec.ts | 39 ++++++++++++++++++ .../view-filter-resolver.integration-spec.ts | 41 +++++++++++++++++++ .../view-group-resolver.integration-spec.ts | 40 ++++++++++++++++++ .../view/view-resolver.integration-spec.ts | 31 ++++++++++++++ .../view-sort-resolver.integration-spec.ts | 36 ++++++++++++++++ ...stroy-view-field-operation-factory.util.ts | 16 ++++++++ ...iew-filter-group-operation-factory.util.ts | 16 ++++++++ ...troy-view-filter-operation-factory.util.ts | 16 ++++++++ ...stroy-view-group-operation-factory.util.ts | 16 ++++++++ .../destroy-view-operation-factory.util.ts | 16 ++++++++ ...estroy-view-sort-operation-factory.util.ts | 16 ++++++++ 36 files changed, 624 insertions(+) create mode 100644 packages/twenty-server/test/integration/graphql/utils/destroy-view-field-operation-factory.util.ts create mode 100644 packages/twenty-server/test/integration/graphql/utils/destroy-view-filter-group-operation-factory.util.ts create mode 100644 packages/twenty-server/test/integration/graphql/utils/destroy-view-filter-operation-factory.util.ts create mode 100644 packages/twenty-server/test/integration/graphql/utils/destroy-view-group-operation-factory.util.ts create mode 100644 packages/twenty-server/test/integration/graphql/utils/destroy-view-operation-factory.util.ts create mode 100644 packages/twenty-server/test/integration/graphql/utils/destroy-view-sort-operation-factory.util.ts diff --git a/packages/twenty-server/src/engine/core-modules/view/controllers/view-field.controller.ts b/packages/twenty-server/src/engine/core-modules/view/controllers/view-field.controller.ts index b33f4ce807..7ea3f5163d 100644 --- a/packages/twenty-server/src/engine/core-modules/view/controllers/view-field.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/view/controllers/view-field.controller.ts @@ -87,4 +87,6 @@ export class ViewFieldController { return { success: isDefined(deletedViewField) }; } + + // TODO: the destroy endpoint will be implemented when we settle on a strategy } diff --git a/packages/twenty-server/src/engine/core-modules/view/controllers/view-filter-group.controller.ts b/packages/twenty-server/src/engine/core-modules/view/controllers/view-filter-group.controller.ts index 53e2df9c05..04645411aa 100644 --- a/packages/twenty-server/src/engine/core-modules/view/controllers/view-filter-group.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/view/controllers/view-filter-group.controller.ts @@ -89,4 +89,6 @@ export class ViewFilterGroupController { return { success: isDefined(deletedViewFilterGroup) }; } + + // TODO: the destroy endpoint will be implemented when we settle on a strategy } diff --git a/packages/twenty-server/src/engine/core-modules/view/controllers/view-filter.controller.ts b/packages/twenty-server/src/engine/core-modules/view/controllers/view-filter.controller.ts index c1ca246a38..d83754d2fc 100644 --- a/packages/twenty-server/src/engine/core-modules/view/controllers/view-filter.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/view/controllers/view-filter.controller.ts @@ -87,4 +87,6 @@ export class ViewFilterController { return { success: isDefined(deletedViewFilter) }; } + + // TODO: the destroy endpoint will be implemented when we settle on a strategy } diff --git a/packages/twenty-server/src/engine/core-modules/view/controllers/view-group.controller.ts b/packages/twenty-server/src/engine/core-modules/view/controllers/view-group.controller.ts index 4ed626e6b7..98ca90e584 100644 --- a/packages/twenty-server/src/engine/core-modules/view/controllers/view-group.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/view/controllers/view-group.controller.ts @@ -87,4 +87,6 @@ export class ViewGroupController { return { success: isDefined(deletedViewGroup) }; } + + // TODO: the destroy endpoint will be implemented when we settle on a strategy } diff --git a/packages/twenty-server/src/engine/core-modules/view/controllers/view-sort.controller.ts b/packages/twenty-server/src/engine/core-modules/view/controllers/view-sort.controller.ts index e59f6aca52..e03086b247 100644 --- a/packages/twenty-server/src/engine/core-modules/view/controllers/view-sort.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/view/controllers/view-sort.controller.ts @@ -84,4 +84,6 @@ export class ViewSortController { return { success: isDefined(deletedViewSort) }; } + + // TODO: the destroy endpoint will be implemented when we settle on a strategy } diff --git a/packages/twenty-server/src/engine/core-modules/view/controllers/view.controller.ts b/packages/twenty-server/src/engine/core-modules/view/controllers/view.controller.ts index 0bd88c00a3..b433b71a49 100644 --- a/packages/twenty-server/src/engine/core-modules/view/controllers/view.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/view/controllers/view.controller.ts @@ -84,4 +84,6 @@ export class ViewController { return { success: isDefined(deletedView) }; } + + // TODO: the destroy endpoint will be implemented when we settle on a strategy } diff --git a/packages/twenty-server/src/engine/core-modules/view/resolvers/view-field.resolver.ts b/packages/twenty-server/src/engine/core-modules/view/resolvers/view-field.resolver.ts index 2e1ac1ba59..81742e530b 100644 --- a/packages/twenty-server/src/engine/core-modules/view/resolvers/view-field.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/view/resolvers/view-field.resolver.ts @@ -67,4 +67,17 @@ export class ViewFieldResolver { return isDefined(deletedViewField); } + + @Mutation(() => Boolean) + async destroyCoreViewField( + @Args('id', { type: () => String }) id: string, + @AuthWorkspace() workspace: Workspace, + ): Promise { + const deletedViewField = await this.viewFieldService.destroy( + id, + workspace.id, + ); + + return isDefined(deletedViewField); + } } diff --git a/packages/twenty-server/src/engine/core-modules/view/resolvers/view-filter-group.resolver.ts b/packages/twenty-server/src/engine/core-modules/view/resolvers/view-filter-group.resolver.ts index 1c5f2e313d..142e35a0e3 100644 --- a/packages/twenty-server/src/engine/core-modules/view/resolvers/view-filter-group.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/view/resolvers/view-filter-group.resolver.ts @@ -73,4 +73,17 @@ export class ViewFilterGroupResolver { return isDefined(deletedViewFilterGroup); } + + @Mutation(() => Boolean) + async destroyCoreViewFilterGroup( + @Args('id', { type: () => String }) id: string, + @AuthWorkspace() workspace: Workspace, + ): Promise { + const deletedViewFilterGroup = await this.viewFilterGroupService.destroy( + id, + workspace.id, + ); + + return isDefined(deletedViewFilterGroup); + } } diff --git a/packages/twenty-server/src/engine/core-modules/view/resolvers/view-filter.resolver.ts b/packages/twenty-server/src/engine/core-modules/view/resolvers/view-filter.resolver.ts index 80c8a90bc8..eedcbc9a2b 100644 --- a/packages/twenty-server/src/engine/core-modules/view/resolvers/view-filter.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/view/resolvers/view-filter.resolver.ts @@ -71,4 +71,17 @@ export class ViewFilterResolver { return isDefined(deletedViewFilter); } + + @Mutation(() => Boolean) + async destroyCoreViewFilter( + @Args('id', { type: () => String }) id: string, + @AuthWorkspace() workspace: Workspace, + ): Promise { + const deletedViewFilter = await this.viewFilterService.destroy( + id, + workspace.id, + ); + + return isDefined(deletedViewFilter); + } } diff --git a/packages/twenty-server/src/engine/core-modules/view/resolvers/view-group.resolver.ts b/packages/twenty-server/src/engine/core-modules/view/resolvers/view-group.resolver.ts index ca3125f478..26c2c3eca2 100644 --- a/packages/twenty-server/src/engine/core-modules/view/resolvers/view-group.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/view/resolvers/view-group.resolver.ts @@ -71,4 +71,17 @@ export class ViewGroupResolver { return isDefined(deletedViewGroup); } + + @Mutation(() => Boolean) + async destroyCoreViewGroup( + @Args('id', { type: () => String }) id: string, + @AuthWorkspace() workspace: Workspace, + ): Promise { + const deletedViewGroup = await this.viewGroupService.destroy( + id, + workspace.id, + ); + + return isDefined(deletedViewGroup); + } } diff --git a/packages/twenty-server/src/engine/core-modules/view/resolvers/view-sort.resolver.ts b/packages/twenty-server/src/engine/core-modules/view/resolvers/view-sort.resolver.ts index 7bbfc666ad..8f5c44d2b7 100644 --- a/packages/twenty-server/src/engine/core-modules/view/resolvers/view-sort.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/view/resolvers/view-sort.resolver.ts @@ -68,4 +68,17 @@ export class ViewSortResolver { return isDefined(deletedViewSort); } + + @Mutation(() => Boolean) + async destroyCoreViewSort( + @Args('id', { type: () => String }) id: string, + @AuthWorkspace() workspace: Workspace, + ): Promise { + const deletedViewSort = await this.viewSortService.destroy( + id, + workspace.id, + ); + + return isDefined(deletedViewSort); + } } diff --git a/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts b/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts index 8d00fe246b..719071cc15 100644 --- a/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/view/resolvers/view.resolver.ts @@ -71,4 +71,14 @@ export class ViewResolver { return isDefined(deletedView); } + + @Mutation(() => Boolean) + async destroyCoreView( + @Args('id', { type: () => String }) id: string, + @AuthWorkspace() workspace: Workspace, + ): Promise { + const deletedView = await this.viewService.destroy(id, workspace.id); + + return isDefined(deletedView); + } } diff --git a/packages/twenty-server/src/engine/core-modules/view/services/tests/view-field.service.spec.ts b/packages/twenty-server/src/engine/core-modules/view/services/tests/view-field.service.spec.ts index dc63477080..a43ff8ebf6 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/tests/view-field.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/tests/view-field.service.spec.ts @@ -294,4 +294,20 @@ describe('ViewFieldService', () => { ); }); }); + + describe('destroy', () => { + it('should destroy a view field successfully', async () => { + const id = 'view-field-id'; + const workspaceId = 'workspace-id'; + + jest.spyOn(viewFieldService, 'findById').mockResolvedValue(mockViewField); + jest.spyOn(viewFieldRepository, 'delete').mockResolvedValue({} as any); + + const result = await viewFieldService.destroy(id, workspaceId); + + expect(viewFieldService.findById).toHaveBeenCalledWith(id, workspaceId); + expect(viewFieldRepository.delete).toHaveBeenCalledWith(id); + expect(result).toEqual(true); + }); + }); }); diff --git a/packages/twenty-server/src/engine/core-modules/view/services/tests/view-filter-group.service.spec.ts b/packages/twenty-server/src/engine/core-modules/view/services/tests/view-filter-group.service.spec.ts index 2ab797c5d2..3ccc81fee2 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/tests/view-filter-group.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/tests/view-filter-group.service.spec.ts @@ -41,6 +41,7 @@ describe('ViewFilterGroupService', () => { create: jest.fn(), save: jest.fn(), softDelete: jest.fn(), + delete: jest.fn(), }, }, ], @@ -329,4 +330,27 @@ describe('ViewFilterGroupService', () => { ); }); }); + + describe('destroy', () => { + it('should destroy a view filter group successfully', async () => { + const id = 'view-filter-group-id'; + const workspaceId = 'workspace-id'; + + jest + .spyOn(viewFilterGroupService, 'findById') + .mockResolvedValue(mockViewFilterGroup); + jest + .spyOn(viewFilterGroupRepository, 'delete') + .mockResolvedValue({} as any); + + const result = await viewFilterGroupService.destroy(id, workspaceId); + + expect(viewFilterGroupService.findById).toHaveBeenCalledWith( + id, + workspaceId, + ); + expect(viewFilterGroupRepository.delete).toHaveBeenCalledWith(id); + expect(result).toEqual(true); + }); + }); }); diff --git a/packages/twenty-server/src/engine/core-modules/view/services/tests/view-filter.service.spec.ts b/packages/twenty-server/src/engine/core-modules/view/services/tests/view-filter.service.spec.ts index 732cf5c1f6..14fe3dbf09 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/tests/view-filter.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/tests/view-filter.service.spec.ts @@ -43,6 +43,7 @@ describe('ViewFilterService', () => { create: jest.fn(), save: jest.fn(), softDelete: jest.fn(), + delete: jest.fn(), }, }, ], @@ -310,4 +311,22 @@ describe('ViewFilterService', () => { ); }); }); + + describe('destroy', () => { + it('should destroy a view filter successfully', async () => { + const id = 'view-filter-id'; + const workspaceId = 'workspace-id'; + + jest + .spyOn(viewFilterService, 'findById') + .mockResolvedValue(mockViewFilter); + jest.spyOn(viewFilterRepository, 'delete').mockResolvedValue({} as any); + + const result = await viewFilterService.destroy(id, workspaceId); + + expect(viewFilterService.findById).toHaveBeenCalledWith(id, workspaceId); + expect(viewFilterRepository.delete).toHaveBeenCalledWith(id); + expect(result).toEqual(true); + }); + }); }); diff --git a/packages/twenty-server/src/engine/core-modules/view/services/tests/view-group.service.spec.ts b/packages/twenty-server/src/engine/core-modules/view/services/tests/view-group.service.spec.ts index 8c49482b45..f383a1da7d 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/tests/view-group.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/tests/view-group.service.spec.ts @@ -42,6 +42,7 @@ describe('ViewGroupService', () => { create: jest.fn(), save: jest.fn(), softDelete: jest.fn(), + delete: jest.fn(), }, }, ], @@ -294,4 +295,20 @@ describe('ViewGroupService', () => { ); }); }); + + describe('destroy', () => { + it('should destroy a view group successfully', async () => { + const id = 'view-group-id'; + const workspaceId = 'workspace-id'; + + jest.spyOn(viewGroupService, 'findById').mockResolvedValue(mockViewGroup); + jest.spyOn(viewGroupRepository, 'delete').mockResolvedValue({} as any); + + const result = await viewGroupService.destroy(id, workspaceId); + + expect(viewGroupService.findById).toHaveBeenCalledWith(id, workspaceId); + expect(viewGroupRepository.delete).toHaveBeenCalledWith(id); + expect(result).toEqual(true); + }); + }); }); diff --git a/packages/twenty-server/src/engine/core-modules/view/services/tests/view-sort.service.spec.ts b/packages/twenty-server/src/engine/core-modules/view/services/tests/view-sort.service.spec.ts index d01421af37..0a3dc4613e 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/tests/view-sort.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/tests/view-sort.service.spec.ts @@ -41,6 +41,7 @@ describe('ViewSortService', () => { create: jest.fn(), save: jest.fn(), softDelete: jest.fn(), + delete: jest.fn(), }, }, ], @@ -281,4 +282,20 @@ describe('ViewSortService', () => { ); }); }); + + describe('destroy', () => { + it('should destroy a view sort successfully', async () => { + const id = 'view-sort-id'; + const workspaceId = 'workspace-id'; + + jest.spyOn(viewSortService, 'findById').mockResolvedValue(mockViewSort); + jest.spyOn(viewSortRepository, 'delete').mockResolvedValue({} as any); + + const result = await viewSortService.destroy(id, workspaceId); + + expect(viewSortService.findById).toHaveBeenCalledWith(id, workspaceId); + expect(viewSortRepository.delete).toHaveBeenCalledWith(id); + expect(result).toEqual(true); + }); + }); }); diff --git a/packages/twenty-server/src/engine/core-modules/view/services/tests/view.service.spec.ts b/packages/twenty-server/src/engine/core-modules/view/services/tests/view.service.spec.ts index f9c72cc076..dac3e5a75e 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/tests/view.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/tests/view.service.spec.ts @@ -50,6 +50,7 @@ describe('ViewService', () => { create: jest.fn(), save: jest.fn(), softDelete: jest.fn(), + delete: jest.fn(), }, }, ], @@ -295,4 +296,20 @@ describe('ViewService', () => { ); }); }); + + describe('destroy', () => { + it('should destroy a view successfully', async () => { + const id = 'view-id'; + const workspaceId = 'workspace-id'; + + jest.spyOn(viewService, 'findById').mockResolvedValue(mockView); + jest.spyOn(viewRepository, 'delete').mockResolvedValue({} as any); + + const result = await viewService.destroy(id, workspaceId); + + expect(viewService.findById).toHaveBeenCalledWith(id, workspaceId); + expect(viewRepository.delete).toHaveBeenCalledWith(id); + expect(result).toEqual(true); + }); + }); }); diff --git a/packages/twenty-server/src/engine/core-modules/view/services/view-field.service.ts b/packages/twenty-server/src/engine/core-modules/view/services/view-field.service.ts index 97a1439576..20d8dc69b7 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/view-field.service.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/view-field.service.ts @@ -149,4 +149,22 @@ export class ViewFieldService { return viewField; } + + async destroy(id: string, workspaceId: string): Promise { + const viewField = await this.findById(id, workspaceId); + + if (!isDefined(viewField)) { + throw new ViewFieldException( + generateViewFieldExceptionMessage( + ViewFieldExceptionMessageKey.VIEW_FIELD_NOT_FOUND, + id, + ), + ViewFieldExceptionCode.VIEW_FIELD_NOT_FOUND, + ); + } + + await this.viewFieldRepository.delete(id); + + return true; + } } diff --git a/packages/twenty-server/src/engine/core-modules/view/services/view-filter-group.service.ts b/packages/twenty-server/src/engine/core-modules/view/services/view-filter-group.service.ts index 4a56a643c7..3632fce0c5 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/view-filter-group.service.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/view-filter-group.service.ts @@ -161,4 +161,22 @@ export class ViewFilterGroupService { return viewFilterGroup; } + + async destroy(id: string, workspaceId: string): Promise { + const viewFilterGroup = await this.findById(id, workspaceId); + + if (!isDefined(viewFilterGroup)) { + throw new ViewFilterGroupException( + generateViewFilterGroupExceptionMessage( + ViewFilterGroupExceptionMessageKey.VIEW_FILTER_GROUP_NOT_FOUND, + id, + ), + ViewFilterGroupExceptionCode.VIEW_FILTER_GROUP_NOT_FOUND, + ); + } + + await this.viewFilterGroupRepository.delete(id); + + return true; + } } diff --git a/packages/twenty-server/src/engine/core-modules/view/services/view-filter.service.ts b/packages/twenty-server/src/engine/core-modules/view/services/view-filter.service.ts index dd3378be77..2ec90823e1 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/view-filter.service.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/view-filter.service.ts @@ -149,4 +149,22 @@ export class ViewFilterService { return viewFilter; } + + async destroy(id: string, workspaceId: string): Promise { + const viewFilter = await this.findById(id, workspaceId); + + if (!isDefined(viewFilter)) { + throw new ViewFilterException( + generateViewFilterExceptionMessage( + ViewFilterExceptionMessageKey.VIEW_FILTER_NOT_FOUND, + id, + ), + ViewFilterExceptionCode.VIEW_FILTER_NOT_FOUND, + ); + } + + await this.viewFilterRepository.delete(id); + + return true; + } } diff --git a/packages/twenty-server/src/engine/core-modules/view/services/view-group.service.ts b/packages/twenty-server/src/engine/core-modules/view/services/view-group.service.ts index d6109628b6..85d95b5cbf 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/view-group.service.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/view-group.service.ts @@ -149,4 +149,22 @@ export class ViewGroupService { return viewGroup; } + + async destroy(id: string, workspaceId: string): Promise { + const viewGroup = await this.findById(id, workspaceId); + + if (!isDefined(viewGroup)) { + throw new ViewGroupException( + generateViewGroupExceptionMessage( + ViewGroupExceptionMessageKey.VIEW_GROUP_NOT_FOUND, + id, + ), + ViewGroupExceptionCode.VIEW_GROUP_NOT_FOUND, + ); + } + + await this.viewGroupRepository.delete(id); + + return true; + } } diff --git a/packages/twenty-server/src/engine/core-modules/view/services/view-sort.service.ts b/packages/twenty-server/src/engine/core-modules/view/services/view-sort.service.ts index a150665762..ac4fb052ea 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/view-sort.service.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/view-sort.service.ts @@ -144,4 +144,22 @@ export class ViewSortService { return viewSort; } + + async destroy(id: string, workspaceId: string): Promise { + const viewSort = await this.findById(id, workspaceId); + + if (!isDefined(viewSort)) { + throw new ViewSortException( + generateViewSortExceptionMessage( + ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND, + id, + ), + ViewSortExceptionCode.VIEW_SORT_NOT_FOUND, + ); + } + + await this.viewSortRepository.delete(id); + + return true; + } } diff --git a/packages/twenty-server/src/engine/core-modules/view/services/view.service.ts b/packages/twenty-server/src/engine/core-modules/view/services/view.service.ts index 4c9b9168a6..bc3d66fd07 100644 --- a/packages/twenty-server/src/engine/core-modules/view/services/view.service.ts +++ b/packages/twenty-server/src/engine/core-modules/view/services/view.service.ts @@ -156,4 +156,22 @@ export class ViewService { return view; } + + async destroy(id: string, workspaceId: string): Promise { + const view = await this.findById(id, workspaceId); + + if (!isDefined(view)) { + throw new ViewException( + generateViewExceptionMessage( + ViewExceptionMessageKey.VIEW_NOT_FOUND, + id, + ), + ViewExceptionCode.VIEW_NOT_FOUND, + ); + } + + await this.viewRepository.delete(id); + + return true; + } } diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-field-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-field-resolver.integration-spec.ts index 59731cff51..7b94c34282 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-field-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-field-resolver.integration-spec.ts @@ -4,6 +4,7 @@ import { } from 'test/integration/constants/test-view-ids.constants'; import { createViewFieldOperationFactory } from 'test/integration/graphql/utils/create-view-field-operation-factory.util'; import { deleteViewFieldOperationFactory } from 'test/integration/graphql/utils/delete-view-field-operation-factory.util'; +import { destroyViewFieldOperationFactory } from 'test/integration/graphql/utils/destroy-view-field-operation-factory.util'; import { findViewFieldsOperationFactory } from 'test/integration/graphql/utils/find-view-fields-operation-factory.util'; import { assertGraphQLErrorResponse, @@ -210,4 +211,39 @@ describe('View Field Resolver', () => { ); }); }); + + describe('destroyCoreViewField', () => { + it('should destroy an existing view field', async () => { + const fieldData = createViewFieldData(testViewId); + const createOperation = createViewFieldOperationFactory({ + data: fieldData, + }); + const createResponse = await makeGraphqlAPIRequest(createOperation); + const viewField = createResponse.body.data.createCoreViewField; + + const destroyOperation = destroyViewFieldOperationFactory({ + viewFieldId: viewField.id, + }); + const response = await makeGraphqlAPIRequest(destroyOperation); + + assertGraphQLSuccessfulResponse(response); + expect(response.body.data.destroyCoreViewField).toBe(true); + }); + + it('should throw an error when destroying non-existent view field', async () => { + const operation = destroyViewFieldOperationFactory({ + viewFieldId: TEST_NOT_EXISTING_VIEW_FIELD_ID, + }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLErrorResponse( + response, + ErrorCode.NOT_FOUND, + generateViewFieldExceptionMessage( + ViewFieldExceptionMessageKey.VIEW_FIELD_NOT_FOUND, + TEST_NOT_EXISTING_VIEW_FIELD_ID, + ), + ); + }); + }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts index 278c08f391..b2d49b5b60 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-group-resolver.integration-spec.ts @@ -1,6 +1,7 @@ import { TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID } from 'test/integration/constants/test-view-ids.constants'; import { createViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/create-view-filter-group-operation-factory.util'; import { deleteViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/delete-view-filter-group-operation-factory.util'; +import { destroyViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/destroy-view-filter-group-operation-factory.util'; import { findViewFilterGroupOperationFactory } from 'test/integration/graphql/utils/find-view-filter-group-operation-factory.util'; import { findViewFilterGroupsOperationFactory } from 'test/integration/graphql/utils/find-view-filter-groups-operation-factory.util'; import { @@ -401,4 +402,42 @@ describe('View Filter Group Resolver', () => { ); }); }); + + describe('destroyCoreViewFilterGroup', () => { + it('should destroy an existing filter group', async () => { + const filterGroupData = createViewFilterGroupData(testViewId, { + logicalOperator: ViewFilterGroupLogicalOperator.AND, + }); + const createOperation = createViewFilterGroupOperationFactory({ + data: filterGroupData, + }); + const createResponse = await makeGraphqlAPIRequest(createOperation); + const filterGroupId = + createResponse.body.data.createCoreViewFilterGroup.id; + + const destroyOperation = destroyViewFilterGroupOperationFactory({ + viewFilterGroupId: filterGroupId, + }); + const response = await makeGraphqlAPIRequest(destroyOperation); + + assertGraphQLSuccessfulResponse(response); + expect(response.body.data.destroyCoreViewFilterGroup).toBe(true); + }); + + it('should throw an error when destroying non-existent filter group', async () => { + const operation = destroyViewFilterGroupOperationFactory({ + viewFilterGroupId: TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID, + }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLErrorResponse( + response, + ErrorCode.NOT_FOUND, + generateViewFilterGroupExceptionMessage( + ViewFilterGroupExceptionMessageKey.VIEW_FILTER_GROUP_NOT_FOUND, + TEST_NOT_EXISTING_VIEW_FILTER_GROUP_ID, + ), + ); + }); + }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts index 7d659f312f..9d56a71cab 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-filter-resolver.integration-spec.ts @@ -4,6 +4,7 @@ import { } from 'test/integration/constants/test-view-ids.constants'; import { createViewFilterOperationFactory } from 'test/integration/graphql/utils/create-view-filter-operation-factory.util'; import { deleteViewFilterOperationFactory } from 'test/integration/graphql/utils/delete-view-filter-operation-factory.util'; +import { destroyViewFilterOperationFactory } from 'test/integration/graphql/utils/destroy-view-filter-operation-factory.util'; import { findViewFiltersOperationFactory } from 'test/integration/graphql/utils/find-view-filters-operation-factory.util'; import { assertGraphQLErrorResponse, @@ -222,4 +223,44 @@ describe('View Filter Resolver', () => { ); }); }); + + describe('destroyCoreViewFilter', () => { + it('should destroy an existing view filter', async () => { + const createOperation = createViewFilterOperationFactory({ + data: createViewFilterData(testViewId, { + operand: ViewFilterOperand.CONTAINS, + value: 'to destroy', + }), + }); + + const createResponse = await makeGraphqlAPIRequest(createOperation); + + const viewFilterId = createResponse.body.data.createCoreViewFilter.id; + + const destroyOperation = destroyViewFilterOperationFactory({ + viewFilterId: viewFilterId, + }); + + const response = await makeGraphqlAPIRequest(destroyOperation); + + assertGraphQLSuccessfulResponse(response); + expect(response.body.data.destroyCoreViewFilter).toBe(true); + }); + + it('should throw an error when destroying non-existent view filter', async () => { + const operation = destroyViewFilterOperationFactory({ + viewFilterId: TEST_NOT_EXISTING_VIEW_FILTER_ID, + }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLErrorResponse( + response, + ErrorCode.NOT_FOUND, + generateViewFilterExceptionMessage( + ViewFilterExceptionMessageKey.VIEW_FILTER_NOT_FOUND, + TEST_NOT_EXISTING_VIEW_FILTER_ID, + ), + ); + }); + }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts index e6f21d6818..7f85744838 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-group-resolver.integration-spec.ts @@ -4,6 +4,7 @@ import { } from 'test/integration/constants/test-view-ids.constants'; import { createViewGroupOperationFactory } from 'test/integration/graphql/utils/create-view-group-operation-factory.util'; import { deleteViewGroupOperationFactory } from 'test/integration/graphql/utils/delete-view-group-operation-factory.util'; +import { destroyViewGroupOperationFactory } from 'test/integration/graphql/utils/destroy-view-group-operation-factory.util'; import { findViewGroupsOperationFactory } from 'test/integration/graphql/utils/find-view-groups-operation-factory.util'; import { assertGraphQLErrorResponse, @@ -215,4 +216,43 @@ describe('View Group Resolver', () => { ); }); }); + + describe('destroyCoreViewGroup', () => { + it('should destroy an existing view group', async () => { + const groupData = createViewGroupData(testViewId, { + isVisible: true, + fieldValue: 'to destroy', + position: 0, + }); + const createOperation = createViewGroupOperationFactory({ + data: groupData, + }); + const createResponse = await makeGraphqlAPIRequest(createOperation); + const viewGroup = createResponse.body.data.createCoreViewGroup; + + const destroyOperation = destroyViewGroupOperationFactory({ + viewGroupId: viewGroup.id, + }); + const response = await makeGraphqlAPIRequest(destroyOperation); + + assertGraphQLSuccessfulResponse(response); + expect(response.body.data.destroyCoreViewGroup).toBe(true); + }); + + it('should throw an error when destroying non-existent view group', async () => { + const operation = destroyViewGroupOperationFactory({ + viewGroupId: TEST_NOT_EXISTING_VIEW_GROUP_ID, + }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLErrorResponse( + response, + ErrorCode.NOT_FOUND, + generateViewGroupExceptionMessage( + ViewGroupExceptionMessageKey.VIEW_GROUP_NOT_FOUND, + TEST_NOT_EXISTING_VIEW_GROUP_ID, + ), + ); + }); + }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-resolver.integration-spec.ts index e7050a5b38..ed19d756d6 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-resolver.integration-spec.ts @@ -5,6 +5,7 @@ import { } from 'test/integration/constants/test-view-ids.constants'; import { createViewOperationFactory } from 'test/integration/graphql/utils/create-view-operation-factory.util'; import { deleteViewOperationFactory } from 'test/integration/graphql/utils/delete-view-operation-factory.util'; +import { destroyViewOperationFactory } from 'test/integration/graphql/utils/destroy-view-operation-factory.util'; import { findViewOperationFactory } from 'test/integration/graphql/utils/find-view-operation-factory.util'; import { findViewsOperationFactory } from 'test/integration/graphql/utils/find-views-operation-factory.util'; import { @@ -258,4 +259,34 @@ describe('View Resolver', () => { ); }); }); + + describe('destroyCoreView', () => { + it('should destroy an existing view', async () => { + const view = await createTestViewWithGraphQL({ + name: 'View to Destroy', + }); + + const destroyOperation = destroyViewOperationFactory({ viewId: view.id }); + const destroyResponse = await makeGraphqlAPIRequest(destroyOperation); + + assertGraphQLSuccessfulResponse(destroyResponse); + expect(destroyResponse.body.data.destroyCoreView).toBe(true); + }); + + it('should throw an error when destroying non-existent view', async () => { + const operation = destroyViewOperationFactory({ + viewId: TEST_NOT_EXISTING_VIEW_ID, + }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLErrorResponse( + response, + ErrorCode.NOT_FOUND, + generateViewExceptionMessage( + ViewExceptionMessageKey.VIEW_NOT_FOUND, + TEST_NOT_EXISTING_VIEW_ID, + ), + ); + }); + }); }); diff --git a/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts index be36f5c02a..26d221af70 100644 --- a/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/view/view-sort-resolver.integration-spec.ts @@ -4,6 +4,7 @@ import { } from 'test/integration/constants/test-view-ids.constants'; import { createViewSortOperationFactory } from 'test/integration/graphql/utils/create-view-sort-operation-factory.util'; import { deleteViewSortOperationFactory } from 'test/integration/graphql/utils/delete-view-sort-operation-factory.util'; +import { destroyViewSortOperationFactory } from 'test/integration/graphql/utils/destroy-view-sort-operation-factory.util'; import { findViewSortsOperationFactory } from 'test/integration/graphql/utils/find-view-sorts-operation-factory.util'; import { assertGraphQLErrorResponse, @@ -191,4 +192,39 @@ describe('View Sort Resolver', () => { ); }); }); + + describe('destroyCoreViewSort', () => { + it('should destroy an existing view sort', async () => { + const sortData = createViewSortData(testViewId); + const createOperation = createViewSortOperationFactory({ + data: sortData, + }); + const createResponse = await makeGraphqlAPIRequest(createOperation); + const viewSort = createResponse.body.data.createCoreViewSort; + + const destroyOperation = destroyViewSortOperationFactory({ + viewSortId: viewSort.id, + }); + const response = await makeGraphqlAPIRequest(destroyOperation); + + assertGraphQLSuccessfulResponse(response); + expect(response.body.data.destroyCoreViewSort).toBe(true); + }); + + it('should throw an error when destroying non-existent view sort', async () => { + const operation = destroyViewSortOperationFactory({ + viewSortId: TEST_NOT_EXISTING_VIEW_SORT_ID, + }); + const response = await makeGraphqlAPIRequest(operation); + + assertGraphQLErrorResponse( + response, + ErrorCode.NOT_FOUND, + generateViewSortExceptionMessage( + ViewSortExceptionMessageKey.VIEW_SORT_NOT_FOUND, + TEST_NOT_EXISTING_VIEW_SORT_ID, + ), + ); + }); + }); }); diff --git a/packages/twenty-server/test/integration/graphql/utils/destroy-view-field-operation-factory.util.ts b/packages/twenty-server/test/integration/graphql/utils/destroy-view-field-operation-factory.util.ts new file mode 100644 index 0000000000..f492121e00 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/utils/destroy-view-field-operation-factory.util.ts @@ -0,0 +1,16 @@ +import gql from 'graphql-tag'; + +export const destroyViewFieldOperationFactory = ({ + viewFieldId, +}: { + viewFieldId: string; +}) => ({ + query: gql` + mutation DestroyCoreViewField($id: String!) { + destroyCoreViewField(id: $id) + } + `, + variables: { + id: viewFieldId, + }, +}); diff --git a/packages/twenty-server/test/integration/graphql/utils/destroy-view-filter-group-operation-factory.util.ts b/packages/twenty-server/test/integration/graphql/utils/destroy-view-filter-group-operation-factory.util.ts new file mode 100644 index 0000000000..9986970b83 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/utils/destroy-view-filter-group-operation-factory.util.ts @@ -0,0 +1,16 @@ +import gql from 'graphql-tag'; + +export const destroyViewFilterGroupOperationFactory = ({ + viewFilterGroupId, +}: { + viewFilterGroupId: string; +}) => ({ + query: gql` + mutation DestroyCoreViewFilterGroup($id: String!) { + destroyCoreViewFilterGroup(id: $id) + } + `, + variables: { + id: viewFilterGroupId, + }, +}); diff --git a/packages/twenty-server/test/integration/graphql/utils/destroy-view-filter-operation-factory.util.ts b/packages/twenty-server/test/integration/graphql/utils/destroy-view-filter-operation-factory.util.ts new file mode 100644 index 0000000000..90b12fce24 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/utils/destroy-view-filter-operation-factory.util.ts @@ -0,0 +1,16 @@ +import gql from 'graphql-tag'; + +export const destroyViewFilterOperationFactory = ({ + viewFilterId, +}: { + viewFilterId: string; +}) => ({ + query: gql` + mutation DestroyCoreViewFilter($id: String!) { + destroyCoreViewFilter(id: $id) + } + `, + variables: { + id: viewFilterId, + }, +}); diff --git a/packages/twenty-server/test/integration/graphql/utils/destroy-view-group-operation-factory.util.ts b/packages/twenty-server/test/integration/graphql/utils/destroy-view-group-operation-factory.util.ts new file mode 100644 index 0000000000..e0b2eb9c51 --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/utils/destroy-view-group-operation-factory.util.ts @@ -0,0 +1,16 @@ +import gql from 'graphql-tag'; + +export const destroyViewGroupOperationFactory = ({ + viewGroupId, +}: { + viewGroupId: string; +}) => ({ + query: gql` + mutation DestroyCoreViewGroup($id: String!) { + destroyCoreViewGroup(id: $id) + } + `, + variables: { + id: viewGroupId, + }, +}); diff --git a/packages/twenty-server/test/integration/graphql/utils/destroy-view-operation-factory.util.ts b/packages/twenty-server/test/integration/graphql/utils/destroy-view-operation-factory.util.ts new file mode 100644 index 0000000000..f29236f33a --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/utils/destroy-view-operation-factory.util.ts @@ -0,0 +1,16 @@ +import gql from 'graphql-tag'; + +export const destroyViewOperationFactory = ({ + viewId, +}: { + viewId: string; +}) => ({ + query: gql` + mutation DestroyCoreView($id: String!) { + destroyCoreView(id: $id) + } + `, + variables: { + id: viewId, + }, +}); diff --git a/packages/twenty-server/test/integration/graphql/utils/destroy-view-sort-operation-factory.util.ts b/packages/twenty-server/test/integration/graphql/utils/destroy-view-sort-operation-factory.util.ts new file mode 100644 index 0000000000..4422072e5a --- /dev/null +++ b/packages/twenty-server/test/integration/graphql/utils/destroy-view-sort-operation-factory.util.ts @@ -0,0 +1,16 @@ +import gql from 'graphql-tag'; + +export const destroyViewSortOperationFactory = ({ + viewSortId, +}: { + viewSortId: string; +}) => ({ + query: gql` + mutation DestroyCoreViewSort($id: String!) { + destroyCoreViewSort(id: $id) + } + `, + variables: { + id: viewSortId, + }, +});