fix: map FlatEntityMaps and WorkspaceMigrationRunner exceptions to proper status codes on REST and GraphQL (#20494)
## Context Calling `POST /rest/views` (and other metadata mutations) currently returns a generic `500` for user-input failures: Ex: 1. **Invalid `objectMetadataId`** — `resolveEntityRelationUniversalIdentifiers` throws `FlatEntityMapsException(RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND)`. Should be `404`. 2. **Missing required field** (e.g. `icon`) — Postgres raises a `NOT NULL` violation, wrapped as `WorkspaceMigrationRunnerException(EXECUTION_FAILED)` carrying a `QueryFailedError`. Should be `400`. Neither was caught by `ViewRestApiExceptionFilter`, so both fell through to `UnhandledExceptionFilter` and were emitted as `500`s without reaching Sentry. Same gap existed on most metadata GraphQL resolvers — only `page-layout*` and `role` resolvers covered `WorkspaceMigrationRunnerException` via `WorkspaceMigrationGraphqlApiExceptionInterceptor`. ## Changes ### New filters REST (`HttpExceptionHandlerService` + Sentry-aware): - `FlatEntityMapsRestApiExceptionFilter` — maps `RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND` / `ENTITY_NOT_FOUND` → `404`, `ENTITY_ALREADY_EXISTS` → `409`, others → `500`. - `WorkspaceMigrationRunnerRestApiExceptionFilter` — for `EXECUTION_FAILED`, unwraps the underlying `metadata` / `workspaceSchema` / `actionTranspilation` error; if it's a `QueryFailedError` it gets remapped to `400` via `HttpExceptionHandlerService`. `APPLICATION_NOT_FOUND` → `404`, `DDL_LOCKED` → `503`, otherwise `500`. GraphQL (graphql-errors + existing formatter): - `FlatEntityMapsGraphqlApiExceptionFilter` — kept as the GraphQL-shaped counterpart (`NotFoundError` / `InternalServerError`). - `WorkspaceMigrationRunnerGraphqlApiExceptionFilter` — reuses `workspaceMigrationRunnerExceptionFormatter` for parity with the existing interceptor. ### Wiring Filters are now declared **per controller / resolver** via `@UseFilters` (no global `APP_FILTER` registration) so they participate in the normal NestJS filter chain instead of being preempted by `UnhandledExceptionFilter`. REST: - `view.controller.ts` — adds `FlatEntityMapsRestApiExceptionFilter` and `WorkspaceMigrationRunnerRestApiExceptionFilter`. GraphQL (14 resolvers, all that mutate flat entities): - `FlatEntityMapsGraphqlApiExceptionFilter` added to: `view`, `view-field`, `view-field-group`, `view-sort`, `view-group`, `view-filter`, `view-filter-group`, `page-layout`, `page-layout-tab`, `page-layout-widget`, `role`, `object-metadata`, `field-metadata`, `index-metadata`. - `WorkspaceMigrationRunnerGraphqlApiExceptionFilter` added to the same list **except** the four already covered by `WorkspaceMigrationGraphqlApiExceptionInterceptor` (`page-layout`, `page-layout-tab`, `page-layout-widget`, `role`) — to avoid double-handling. ## Why per-resolver / per-controller instead of global Earlier attempt to register the filters globally via `APP_FILTER` regressed: NestJS reverses the global filter list and `selectExceptionFilterMetadata` is first-match-wins, so `UnhandledExceptionFilter` (registered last via `app.useGlobalFilters` in `main.ts`) ended up first in the iteration order and preempted every domain-specific filter. The per-resolver / per-controller approach is explicit and predictable. ## Before <img width="953" height="450" alt="Screenshot 2026-05-12 at 15 31 40" src="https://github.com/user-attachments/assets/3c3bc6a8-f6bc-4032-97d0-7243540cfb90" /> ## After <img width="1050" height="598" alt="Screenshot 2026-05-12 at 15 31 17" src="https://github.com/user-attachments/assets/c66c9ce5-d1ea-4f1d-b2fe-07979e2261f7" /> <img width="1068" height="503" alt="Screenshot 2026-05-12 at 15 31 09" src="https://github.com/user-attachments/assets/ddd9eed8-812b-47d6-96cb-b019b807991b" />
This commit is contained in:
+3
-2
@@ -14,6 +14,8 @@ import { QueryFailedError } from 'typeorm';
|
||||
import { type ExceptionHandlerUser } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-user.interface';
|
||||
import { type ExceptionHandlerWorkspace } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-workspace.interface';
|
||||
|
||||
import { CustomError } from 'twenty-shared/utils';
|
||||
|
||||
import { PostgresException } from 'src/engine/api/graphql/workspace-query-runner/utils/postgres-exception';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import {
|
||||
@@ -21,7 +23,6 @@ import {
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import { handleException } from 'src/engine/utils/global-exception-handler.util';
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
interface RequestAndParams {
|
||||
request: Request | null;
|
||||
@@ -121,7 +122,7 @@ export class HttpExceptionHandlerService {
|
||||
statusCode,
|
||||
error: exception.name ?? getErrorNameFromStatusCode(statusCode),
|
||||
messages: [exception?.message],
|
||||
code: exception instanceof CustomException ? exception.code : undefined,
|
||||
code: exception instanceof CustomError ? exception.code : undefined,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
type ArgumentsHost,
|
||||
Catch,
|
||||
type ExceptionFilter,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Response } from 'express';
|
||||
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
import {
|
||||
FlatEntityMapsException,
|
||||
FlatEntityMapsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception';
|
||||
import { type CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
@Injectable()
|
||||
@Catch(FlatEntityMapsException)
|
||||
export class FlatEntityMapsRestApiExceptionFilter implements ExceptionFilter {
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
catch(exception: FlatEntityMapsException, host: ArgumentsHost) {
|
||||
const response = host.switchToHttp().getResponse<Response>();
|
||||
|
||||
switch (exception.code) {
|
||||
case FlatEntityMapsExceptionCode.RELATION_UNIVERSAL_IDENTIFIER_NOT_FOUND:
|
||||
case FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
404,
|
||||
);
|
||||
case FlatEntityMapsExceptionCode.ENTITY_ALREADY_EXISTS:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
409,
|
||||
);
|
||||
default:
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
exception as CustomException,
|
||||
response,
|
||||
500,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-1
@@ -20,16 +20,22 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
|
||||
import { FrontComponentRestApiExceptionFilter } from 'src/engine/metadata-modules/front-component/filters/front-component-rest-api-exception.filter';
|
||||
import {
|
||||
FrontComponentException,
|
||||
FrontComponentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/front-component/front-component.exception';
|
||||
import { FrontComponentService } from 'src/engine/metadata-modules/front-component/front-component.service';
|
||||
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
|
||||
|
||||
@Controller('rest/front-components')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(FrontComponentRestApiExceptionFilter)
|
||||
@UseFilters(
|
||||
FrontComponentRestApiExceptionFilter,
|
||||
FlatEntityMapsRestApiExceptionFilter,
|
||||
WorkspaceMigrationRunnerRestApiExceptionFilter,
|
||||
)
|
||||
export class FrontComponentController {
|
||||
constructor(private readonly frontComponentService: FrontComponentService) {}
|
||||
|
||||
|
||||
+7
-1
@@ -19,6 +19,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
|
||||
import { CreatePageLayoutTabInput } from 'src/engine/metadata-modules/page-layout-tab/dtos/inputs/create-page-layout-tab.input';
|
||||
import { UpdatePageLayoutTabInput } from 'src/engine/metadata-modules/page-layout-tab/dtos/inputs/update-page-layout-tab.input';
|
||||
import { type PageLayoutTabDTO } from 'src/engine/metadata-modules/page-layout-tab/dtos/page-layout-tab.dto';
|
||||
@@ -30,10 +31,15 @@ import {
|
||||
} from 'src/engine/metadata-modules/page-layout-tab/exceptions/page-layout-tab.exception';
|
||||
import { PageLayoutTabRestApiExceptionFilter } from 'src/engine/metadata-modules/page-layout-tab/filters/page-layout-tab-rest-api-exception.filter';
|
||||
import { PageLayoutTabService } from 'src/engine/metadata-modules/page-layout-tab/services/page-layout-tab.service';
|
||||
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
|
||||
|
||||
@Controller('rest/metadata/pageLayoutTabs')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(PageLayoutTabRestApiExceptionFilter)
|
||||
@UseFilters(
|
||||
PageLayoutTabRestApiExceptionFilter,
|
||||
FlatEntityMapsRestApiExceptionFilter,
|
||||
WorkspaceMigrationRunnerRestApiExceptionFilter,
|
||||
)
|
||||
export class PageLayoutTabController {
|
||||
constructor(private readonly pageLayoutTabService: PageLayoutTabService) {}
|
||||
|
||||
|
||||
+7
-1
@@ -19,6 +19,7 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
|
||||
import { CreatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/create-page-layout-widget.input';
|
||||
import { UpdatePageLayoutWidgetInput } from 'src/engine/metadata-modules/page-layout-widget/dtos/inputs/update-page-layout-widget.input';
|
||||
import { type PageLayoutWidgetDTO } from 'src/engine/metadata-modules/page-layout-widget/dtos/page-layout-widget.dto';
|
||||
@@ -30,10 +31,15 @@ import {
|
||||
} from 'src/engine/metadata-modules/page-layout-widget/exceptions/page-layout-widget.exception';
|
||||
import { PageLayoutWidgetRestApiExceptionFilter } from 'src/engine/metadata-modules/page-layout-widget/filters/page-layout-widget-rest-api-exception.filter';
|
||||
import { PageLayoutWidgetService } from 'src/engine/metadata-modules/page-layout-widget/services/page-layout-widget.service';
|
||||
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
|
||||
|
||||
@Controller('rest/metadata/pageLayoutWidgets')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(PageLayoutWidgetRestApiExceptionFilter)
|
||||
@UseFilters(
|
||||
PageLayoutWidgetRestApiExceptionFilter,
|
||||
FlatEntityMapsRestApiExceptionFilter,
|
||||
WorkspaceMigrationRunnerRestApiExceptionFilter,
|
||||
)
|
||||
export class PageLayoutWidgetController {
|
||||
constructor(
|
||||
private readonly pageLayoutWidgetService: PageLayoutWidgetService,
|
||||
|
||||
+7
-1
@@ -19,16 +19,22 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
|
||||
import { CreatePageLayoutInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout.input';
|
||||
import { UpdatePageLayoutInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout.input';
|
||||
import { type PageLayoutDTO } from 'src/engine/metadata-modules/page-layout/dtos/page-layout.dto';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import { PageLayoutRestApiExceptionFilter } from 'src/engine/metadata-modules/page-layout/filters/page-layout-rest-api-exception.filter';
|
||||
import { PageLayoutService } from 'src/engine/metadata-modules/page-layout/services/page-layout.service';
|
||||
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
|
||||
|
||||
@Controller('rest/metadata/pageLayouts')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(PageLayoutRestApiExceptionFilter)
|
||||
@UseFilters(
|
||||
PageLayoutRestApiExceptionFilter,
|
||||
FlatEntityMapsRestApiExceptionFilter,
|
||||
WorkspaceMigrationRunnerRestApiExceptionFilter,
|
||||
)
|
||||
export class PageLayoutController {
|
||||
constructor(private readonly pageLayoutService: PageLayoutService) {}
|
||||
|
||||
|
||||
+7
-1
@@ -17,6 +17,7 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
|
||||
import { CreateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/create-view-field.input';
|
||||
import { UpdateViewFieldInput } from 'src/engine/metadata-modules/view-field/dtos/inputs/update-view-field.input';
|
||||
import { ViewFieldDTO } from 'src/engine/metadata-modules/view-field/dtos/view-field.dto';
|
||||
@@ -32,10 +33,15 @@ import { ViewFieldService } from 'src/engine/metadata-modules/view-field/service
|
||||
import { CreateViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-field-permission.guard';
|
||||
import { DeleteViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-field-permission.guard';
|
||||
import { UpdateViewFieldPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-field-permission.guard';
|
||||
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
|
||||
|
||||
@Controller('rest/metadata/viewFields')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(ViewFieldRestApiExceptionFilter)
|
||||
@UseFilters(
|
||||
ViewFieldRestApiExceptionFilter,
|
||||
FlatEntityMapsRestApiExceptionFilter,
|
||||
WorkspaceMigrationRunnerRestApiExceptionFilter,
|
||||
)
|
||||
export class ViewFieldController {
|
||||
constructor(private readonly viewFieldService: ViewFieldService) {}
|
||||
|
||||
|
||||
+7
-1
@@ -17,6 +17,7 @@ import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspac
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
|
||||
import { type CreateViewFilterGroupInput } from 'src/engine/metadata-modules/view-filter-group/dtos/inputs/create-view-filter-group.input';
|
||||
import { type UpdateViewFilterGroupInput } from 'src/engine/metadata-modules/view-filter-group/dtos/inputs/update-view-filter-group.input';
|
||||
import { type ViewFilterGroupDTO } from 'src/engine/metadata-modules/view-filter-group/dtos/view-filter-group.dto';
|
||||
@@ -32,10 +33,15 @@ import { ViewFilterGroupService } from 'src/engine/metadata-modules/view-filter-
|
||||
import { CreateViewFilterGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-filter-group-permission.guard';
|
||||
import { DeleteViewFilterGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-filter-group-permission.guard';
|
||||
import { UpdateViewFilterGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-filter-group-permission.guard';
|
||||
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
|
||||
|
||||
@Controller('rest/metadata/viewFilterGroups')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(ViewFilterGroupRestApiExceptionFilter)
|
||||
@UseFilters(
|
||||
ViewFilterGroupRestApiExceptionFilter,
|
||||
FlatEntityMapsRestApiExceptionFilter,
|
||||
WorkspaceMigrationRunnerRestApiExceptionFilter,
|
||||
)
|
||||
export class ViewFilterGroupController {
|
||||
constructor(
|
||||
private readonly viewFilterGroupService: ViewFilterGroupService,
|
||||
|
||||
+7
-1
@@ -17,6 +17,7 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
|
||||
import { CreateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/create-view-filter.input';
|
||||
import { UpdateViewFilterInput } from 'src/engine/metadata-modules/view-filter/dtos/inputs/update-view-filter.input';
|
||||
import { ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view-filter.dto';
|
||||
@@ -32,10 +33,15 @@ import { ViewFilterService } from 'src/engine/metadata-modules/view-filter/servi
|
||||
import { CreateViewFilterPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-filter-permission.guard';
|
||||
import { DeleteViewFilterPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-filter-permission.guard';
|
||||
import { UpdateViewFilterPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-filter-permission.guard';
|
||||
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
|
||||
|
||||
@Controller('rest/metadata/viewFilters')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(ViewFilterRestApiExceptionFilter)
|
||||
@UseFilters(
|
||||
ViewFilterRestApiExceptionFilter,
|
||||
FlatEntityMapsRestApiExceptionFilter,
|
||||
WorkspaceMigrationRunnerRestApiExceptionFilter,
|
||||
)
|
||||
export class ViewFilterController {
|
||||
constructor(private readonly viewFilterService: ViewFilterService) {}
|
||||
|
||||
|
||||
+7
-1
@@ -17,6 +17,7 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
|
||||
import { CreateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/create-view-group.input';
|
||||
import { UpdateViewGroupInput } from 'src/engine/metadata-modules/view-group/dtos/inputs/update-view-group.input';
|
||||
import { type ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto';
|
||||
@@ -32,10 +33,15 @@ import { ViewGroupService } from 'src/engine/metadata-modules/view-group/service
|
||||
import { CreateViewGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-group-permission.guard';
|
||||
import { DeleteViewGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-group-permission.guard';
|
||||
import { UpdateViewGroupPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-group-permission.guard';
|
||||
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
|
||||
|
||||
@Controller('rest/metadata/viewGroups')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(ViewGroupRestApiExceptionFilter)
|
||||
@UseFilters(
|
||||
ViewGroupRestApiExceptionFilter,
|
||||
FlatEntityMapsRestApiExceptionFilter,
|
||||
WorkspaceMigrationRunnerRestApiExceptionFilter,
|
||||
)
|
||||
export class ViewGroupController {
|
||||
constructor(private readonly viewGroupService: ViewGroupService) {}
|
||||
|
||||
|
||||
+7
-1
@@ -17,6 +17,7 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
|
||||
import { CreateViewSortPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/create-view-sort-permission.guard';
|
||||
import { DeleteViewSortPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/delete-view-sort-permission.guard';
|
||||
import { UpdateViewSortPermissionGuard } from 'src/engine/metadata-modules/view-permissions/guards/update-view-sort-permission.guard';
|
||||
@@ -31,11 +32,16 @@ import {
|
||||
} from 'src/engine/metadata-modules/view-sort/exceptions/view-sort.exception';
|
||||
import { ViewSortRestApiExceptionFilter } from 'src/engine/metadata-modules/view-sort/filters/view-sort-rest-api-exception.filter';
|
||||
import { ViewSortService } from 'src/engine/metadata-modules/view-sort/services/view-sort.service';
|
||||
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
|
||||
import { ViewSortDirection } from 'twenty-shared/types';
|
||||
|
||||
@Controller('rest/metadata/viewSorts')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(ViewSortRestApiExceptionFilter)
|
||||
@UseFilters(
|
||||
ViewSortRestApiExceptionFilter,
|
||||
FlatEntityMapsRestApiExceptionFilter,
|
||||
WorkspaceMigrationRunnerRestApiExceptionFilter,
|
||||
)
|
||||
export class ViewSortController {
|
||||
constructor(private readonly viewSortService: ViewSortService) {}
|
||||
|
||||
|
||||
+7
-1
@@ -40,10 +40,16 @@ import {
|
||||
} from 'src/engine/metadata-modules/view/exceptions/view.exception';
|
||||
import { ViewRestApiExceptionFilter } from 'src/engine/metadata-modules/view/filters/view-rest-api-exception.filter';
|
||||
import { ViewService } from 'src/engine/metadata-modules/view/services/view.service';
|
||||
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
|
||||
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
|
||||
|
||||
@Controller('rest/metadata/views')
|
||||
@UseGuards(WorkspaceAuthGuard)
|
||||
@UseFilters(ViewRestApiExceptionFilter)
|
||||
@UseFilters(
|
||||
ViewRestApiExceptionFilter,
|
||||
FlatEntityMapsRestApiExceptionFilter,
|
||||
WorkspaceMigrationRunnerRestApiExceptionFilter,
|
||||
)
|
||||
export class ViewController {
|
||||
constructor(
|
||||
private readonly viewService: ViewService,
|
||||
|
||||
+7
-1
@@ -18,10 +18,12 @@ import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorat
|
||||
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { FlatEntityMapsRestApiExceptionFilter } from 'src/engine/metadata-modules/flat-entity/filters/flat-entity-maps-rest-api-exception.filter';
|
||||
import { CreateWebhookInput } from 'src/engine/metadata-modules/webhook/dtos/create-webhook.input';
|
||||
import { UpdateWebhookInput } from 'src/engine/metadata-modules/webhook/dtos/update-webhook.input';
|
||||
import { type WebhookDTO } from 'src/engine/metadata-modules/webhook/dtos/webhook.dto';
|
||||
import { WebhookService } from 'src/engine/metadata-modules/webhook/webhook.service';
|
||||
import { WorkspaceMigrationRunnerRestApiExceptionFilter } from 'src/engine/workspace-manager/workspace-migration/filters/workspace-migration-runner-rest-api-exception.filter';
|
||||
|
||||
@Controller(['rest/webhooks', 'rest/metadata/webhooks'])
|
||||
@UseGuards(
|
||||
@@ -29,7 +31,11 @@ import { WebhookService } from 'src/engine/metadata-modules/webhook/webhook.serv
|
||||
WorkspaceAuthGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.API_KEYS_AND_WEBHOOKS),
|
||||
)
|
||||
@UseFilters(RestApiExceptionFilter)
|
||||
@UseFilters(
|
||||
RestApiExceptionFilter,
|
||||
FlatEntityMapsRestApiExceptionFilter,
|
||||
WorkspaceMigrationRunnerRestApiExceptionFilter,
|
||||
)
|
||||
export class WebhookController {
|
||||
constructor(private readonly webhookService: WebhookService) {}
|
||||
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
type ArgumentsHost,
|
||||
Catch,
|
||||
type ExceptionFilter,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { type Response } from 'express';
|
||||
import { QueryFailedError } from 'typeorm';
|
||||
|
||||
import { HttpExceptionHandlerService } from 'src/engine/core-modules/exception-handler/http-exception-handler.service';
|
||||
import {
|
||||
WorkspaceMigrationRunnerException,
|
||||
WorkspaceMigrationRunnerExceptionCode,
|
||||
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/exceptions/workspace-migration-runner.exception';
|
||||
|
||||
@Injectable()
|
||||
@Catch(WorkspaceMigrationRunnerException)
|
||||
export class WorkspaceMigrationRunnerRestApiExceptionFilter
|
||||
implements ExceptionFilter
|
||||
{
|
||||
constructor(
|
||||
private readonly httpExceptionHandlerService: HttpExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
catch(exception: WorkspaceMigrationRunnerException, host: ArgumentsHost) {
|
||||
const response = host.switchToHttp().getResponse<Response>();
|
||||
|
||||
if (
|
||||
exception.code === WorkspaceMigrationRunnerExceptionCode.EXECUTION_FAILED
|
||||
) {
|
||||
const underlyingError =
|
||||
exception.errors?.metadata ??
|
||||
exception.errors?.workspaceSchema ??
|
||||
exception.errors?.actionTranspilation;
|
||||
|
||||
if (underlyingError instanceof QueryFailedError) {
|
||||
return this.httpExceptionHandlerService.handleError(
|
||||
underlyingError,
|
||||
response,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return this.httpExceptionHandlerService.handleError(exception, response);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user