diff --git a/.cursor/rules/react-state-management-guidelines.md b/.cursor/rules/react-state-management-guidelines.md index cb9909b68..5eb97b24a 100644 --- a/.cursor/rules/react-state-management-guidelines.md +++ b/.cursor/rules/react-state-management-guidelines.md @@ -84,7 +84,7 @@ Twenty uses a combination of Recoil for global state and Apollo Client for serve `; export const GET_USER = gql` - query GetUser($id: ID!) { + query GetUser($id: UUID!) { user(id: $id) { ...UserFields } diff --git a/packages/twenty-e2e-testing/lib/requests/delete-workflow.ts b/packages/twenty-e2e-testing/lib/requests/delete-workflow.ts index 299768d67..a6fdab1aa 100644 --- a/packages/twenty-e2e-testing/lib/requests/delete-workflow.ts +++ b/packages/twenty-e2e-testing/lib/requests/delete-workflow.ts @@ -19,7 +19,7 @@ export const deleteWorkflow = async ({ operationName: 'DeleteOneWorkflow', variables: { idToDelete: workflowId }, query: - 'mutation DeleteOneWorkflow($idToDelete: ID!) {\n deleteWorkflow(id: $idToDelete) {\n __typename\n deletedAt\n id\n }\n}', + 'mutation DeleteOneWorkflow($idToDelete: UUID!) {\n deleteWorkflow(id: $idToDelete) {\n __typename\n deletedAt\n id\n }\n}', }, }); }; diff --git a/packages/twenty-e2e-testing/lib/requests/destroy-workflow.ts b/packages/twenty-e2e-testing/lib/requests/destroy-workflow.ts index 2191a3e3d..6e7b8c67f 100644 --- a/packages/twenty-e2e-testing/lib/requests/destroy-workflow.ts +++ b/packages/twenty-e2e-testing/lib/requests/destroy-workflow.ts @@ -19,7 +19,7 @@ export const destroyWorkflow = async ({ operationName: 'DestroyOneWorkflow', variables: { idToDestroy: workflowId }, query: - 'mutation DestroyOneWorkflow($idToDestroy: ID!) {\n destroyWorkflow(id: $idToDestroy) {\n id\n __typename\n }\n}', + 'mutation DestroyOneWorkflow($idToDestroy: UUID!) {\n destroyWorkflow(id: $idToDestroy) {\n id\n __typename\n }\n}', }, }); }; diff --git a/packages/twenty-front/src/generated/graphql.tsx b/packages/twenty-front/src/generated/graphql.tsx index 3e458bdfd..e663c087c 100644 --- a/packages/twenty-front/src/generated/graphql.tsx +++ b/packages/twenty-front/src/generated/graphql.tsx @@ -736,17 +736,6 @@ export enum HealthIndicatorId { worker = 'worker' } -export type IdFilter = { - eq?: InputMaybe; - gt?: InputMaybe; - gte?: InputMaybe; - in?: InputMaybe>; - is?: InputMaybe; - lt?: InputMaybe; - lte?: InputMaybe; - neq?: InputMaybe; -}; - export enum IdentityProviderType { OIDC = 'OIDC', SAML = 'SAML' @@ -1414,7 +1403,7 @@ export type ObjectRecordFilterInput = { and?: InputMaybe>; createdAt?: InputMaybe; deletedAt?: InputMaybe; - id?: InputMaybe; + id?: InputMaybe; not?: InputMaybe; or?: InputMaybe>; updatedAt?: InputMaybe; @@ -2084,6 +2073,17 @@ export type TransientToken = { transientToken: AuthToken; }; +export type UuidFilter = { + eq?: InputMaybe; + gt?: InputMaybe; + gte?: InputMaybe; + in?: InputMaybe>; + is?: InputMaybe; + lt?: InputMaybe; + lte?: InputMaybe; + neq?: InputMaybe; +}; + export type UuidFilterComparison = { eq?: InputMaybe; gt?: InputMaybe; diff --git a/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx b/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx index af43385f5..0feeefc0d 100644 --- a/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx +++ b/packages/twenty-front/src/modules/activities/tasks/hooks/__tests__/useCompleteTask.test.tsx @@ -27,7 +27,7 @@ const mocks: MockedResponse[] = [ { request: { query: gql` - mutation UpdateOneTask($idToUpdate: ID!, $input: TaskUpdateInput!) { + mutation UpdateOneTask($idToUpdate: UUID!, $input: TaskUpdateInput!) { updateTask(id: $idToUpdate, data: $input) { __typename assignee { diff --git a/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx b/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx index 6052a4390..5f491fc33 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/message-thread/hooks/__tests__/useEmailThreadInCommandMenu.test.tsx @@ -12,7 +12,7 @@ const mocks = [ { request: { query: gql` - query FindOneMessageThread($objectRecordId: ID!) { + query FindOneMessageThread($objectRecordId: UUID!) { messageThread(filter: { id: { eq: $objectRecordId } }) { __typename id diff --git a/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts b/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts index 190fcfc12..286c18226 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/__mocks__/useFavorites.ts @@ -101,7 +101,7 @@ export const sortedFavorites = [ ]; const UPDATE_ONE_FAVORITE_MUTATION = gql` - mutation UpdateOneFavorite($idToUpdate: ID!, $input: FavoriteUpdateInput!) { + mutation UpdateOneFavorite($idToUpdate: UUID!, $input: FavoriteUpdateInput!) { updateFavorite(id: $idToUpdate, data: $input) { __typename company { @@ -859,7 +859,7 @@ export const mocks = [ { request: { query: gql` - mutation DeleteOneFavorite($idToDelete: ID!) { + mutation DeleteOneFavorite($idToDelete: UUID!) { deleteFavorite(id: $idToDelete) { __typename deletedAt diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts index 8c042b848..19314a79c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useDeleteOneRecord.ts @@ -1,7 +1,7 @@ import { gql } from '@apollo/client'; export const query = gql` - mutation DeleteOnePerson($idToDelete: ID!) { + mutation DeleteOnePerson($idToDelete: UUID!) { deletePerson(id: $idToDelete) { __typename deletedAt diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts index 929c016a3..ed071dcd4 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindDuplicateRecords.ts @@ -5,7 +5,7 @@ import { getPeopleRecordConnectionMock } from '~/testing/mock-data/people'; const peopleMock = getPeopleRecordConnectionMock(); export const query = gql` - query FindDuplicatePerson($ids: [ID!]!) { + query FindDuplicatePerson($ids: [UUID!]!) { personDuplicates(ids: $ids) { edges { node { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts index 075dabb05..2178e1c3c 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useFindOneRecord.ts @@ -4,7 +4,7 @@ import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/ import { responseData as person } from './useUpdateOneRecord'; export const query = gql` - query FindOnePerson($objectRecordId: ID!) { + query FindOnePerson($objectRecordId: UUID!) { person(filter: { id: { eq: $objectRecordId } }) { ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } diff --git a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts index 3f1747cdf..f902881d6 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/__mocks__/useUpdateOneRecord.ts @@ -2,7 +2,7 @@ import { PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS } from '@/object-record/hooks/ import { gql } from '@apollo/client'; export const query = gql` - mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) { + mutation UpdateOnePerson($idToUpdate: UUID!, $input: PersonUpdateInput!) { updatePerson(id: $idToUpdate, data: $input) { ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx index 859355818..d6709bfff 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useDeleteOneRecordMutation.test.tsx @@ -5,7 +5,7 @@ import { useDeleteOneRecordMutation } from '@/object-record/hooks/useDeleteOneRe import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` - mutation DeleteOnePerson($idToDelete: ID!) { + mutation DeleteOnePerson($idToDelete: UUID!) { deletePerson(id: $idToDelete) { __typename deletedAt diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx index 10cb78a40..9b26f399d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindDuplicateRecordsQuery.test.tsx @@ -6,7 +6,7 @@ import { useFindDuplicateRecordsQuery } from '@/object-record/hooks/useFindDupli import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` - query FindDuplicatePerson($ids: [ID!]!) { + query FindDuplicatePerson($ids: [UUID!]!) { personDuplicates(ids: $ids) { edges { node { diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx index 32b2a1691..7f0349c0d 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useFindOneRecordQuery.test.tsx @@ -6,7 +6,7 @@ import { useFindOneRecordQuery } from '@/object-record/hooks/useFindOneRecordQue import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const expectedQueryTemplate = ` -query FindOnePerson($objectRecordId: ID!) { +query FindOnePerson($objectRecordId: UUID!) { person(filter: { id: { eq: $objectRecordId } }) { ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } diff --git a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx index be862743e..7d7cb8ab9 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx +++ b/packages/twenty-front/src/modules/object-record/hooks/__tests__/useUpdateOneRecordMutation.test.tsx @@ -7,7 +7,7 @@ import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMeta import { normalizeGQLQuery } from '~/utils/normalizeGQLQuery'; const expectedQueryTemplate = ` -mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) { +mutation UpdateOnePerson($idToUpdate: UUID!, $input: PersonUpdateInput!) { updatePerson(id: $idToUpdate, data: $input) { ${PERSON_FRAGMENT_WITH_DEPTH_ZERO_RELATIONS} } diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecordMutation.ts index 661334bbe..c3d81e9cf 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDeleteOneRecordMutation.ts @@ -4,8 +4,8 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata import { mapSoftDeleteFieldsToGraphQLQuery } from '@/object-metadata/utils/mapSoftDeleteFieldsToGraphQLQuery'; import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation'; import { getDeleteOneRecordMutationResponseField } from '@/object-record/utils/getDeleteOneRecordMutationResponseField'; -import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { capitalize } from 'twenty-shared/utils'; +import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; export const useDeleteOneRecordMutation = ({ objectNameSingular, @@ -27,7 +27,7 @@ export const useDeleteOneRecordMutation = ({ ); const deleteOneRecordMutation = gql` - mutation DeleteOne${capitalizedObjectName}($idToDelete: ID!) { + mutation DeleteOne${capitalizedObjectName}($idToDelete: UUID!) { ${mutationResponseField}(id: $idToDelete) ${mapSoftDeleteFieldsToGraphQLQuery(objectMetadataItem)} } diff --git a/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecordMutation.ts index a5637779f..8e68d75fb 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useDestroyOneRecordMutation.ts @@ -3,8 +3,8 @@ import gql from 'graphql-tag'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation'; import { getDestroyOneRecordMutationResponseField } from '@/object-record/utils/getDestroyOneRecordMutationResponseField'; -import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { capitalize } from 'twenty-shared/utils'; +import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; export const useDestroyOneRecordMutation = ({ objectNameSingular, @@ -26,7 +26,7 @@ export const useDestroyOneRecordMutation = ({ ); const destroyOneRecordMutation = gql` - mutation DestroyOne${capitalizedObjectName}($idToDestroy: ID!) { + mutation DestroyOne${capitalizedObjectName}($idToDestroy: UUID!) { ${mutationResponseField}(id: $idToDestroy) { id } diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicatesRecordsQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicatesRecordsQuery.ts index 4664ef126..61db5cd0f 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicatesRecordsQuery.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindDuplicatesRecordsQuery.ts @@ -22,7 +22,7 @@ export const useFindDuplicateRecordsQuery = ({ const findDuplicateRecordsQuery = gql` query FindDuplicate${capitalize( objectMetadataItem.nameSingular, - )}($ids: [ID!]!) { + )}($ids: [UUID!]!) { ${getFindDuplicateRecordsQueryResponseField( objectMetadataItem.nameSingular, )}(ids: $ids) { diff --git a/packages/twenty-front/src/modules/object-record/hooks/useFindOneRecordQuery.ts b/packages/twenty-front/src/modules/object-record/hooks/useFindOneRecordQuery.ts index b4d65810e..d4c052342 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useFindOneRecordQuery.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useFindOneRecordQuery.ts @@ -25,7 +25,7 @@ export const useFindOneRecordQuery = ({ const findOneRecordQuery = gql` query FindOne${capitalize( objectMetadataItem.nameSingular, - )}($objectRecordId: ID!) { + )}($objectRecordId: UUID!) { ${objectMetadataItem.nameSingular}(filter: { ${ withSoftDeleted diff --git a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordMutation.ts b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordMutation.ts index e812c4023..dd829f6a2 100644 --- a/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordMutation.ts +++ b/packages/twenty-front/src/modules/object-record/hooks/useUpdateOneRecordMutation.ts @@ -8,8 +8,8 @@ import { EMPTY_MUTATION } from '@/object-record/constants/EmptyMutation'; import { RecordGqlOperationGqlRecordFields } from '@/object-record/graphql/types/RecordGqlOperationGqlRecordFields'; import { generateDepthOneRecordGqlFields } from '@/object-record/graphql/utils/generateDepthOneRecordGqlFields'; import { getUpdateOneRecordMutationResponseField } from '@/object-record/utils/getUpdateOneRecordMutationResponseField'; -import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; import { capitalize } from 'twenty-shared/utils'; +import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; export const useUpdateOneRecordMutation = ({ objectNameSingular, @@ -43,7 +43,7 @@ export const useUpdateOneRecordMutation = ({ ); const updateOneRecordMutation = gql` - mutation UpdateOne${capitalizedObjectName}($idToUpdate: ID!, $input: ${capitalizedObjectName}UpdateInput!) { + mutation UpdateOne${capitalizedObjectName}($idToUpdate: UUID!, $input: ${capitalizedObjectName}UpdateInput!) { ${mutationResponseField}(id: $idToUpdate, data: $input) ${mapObjectMetadataToGraphQLQuery( { objectMetadataItems, diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx index 07cfcf9bb..6fde4fedc 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/usePersistField.test.tsx @@ -23,7 +23,7 @@ import { recordStoreFamilySelector } from '@/object-record/record-store/states/s import { getJestMetadataAndApolloMocksWrapper } from '~/testing/jest/getJestMetadataAndApolloMocksWrapper'; const query = gql` - mutation UpdateOnePerson($idToUpdate: ID!, $input: PersonUpdateInput!) { + mutation UpdateOnePerson($idToUpdate: UUID!, $input: PersonUpdateInput!) { updatePerson(id: $idToUpdate, data: $input) { ${PERSON_FRAGMENT_WITH_DEPTH_ONE_RELATIONS} } diff --git a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx index 2ea2a1535..318d80888 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/hooks/__tests__/useToggleEditOnlyInput.test.tsx @@ -22,7 +22,7 @@ const mocks: MockedResponse[] = [ request: { query: gql` mutation UpdateOneCompany( - $idToUpdate: ID! + $idToUpdate: UUID! $input: CompanyUpdateInput! ) { updateCompany(id: $idToUpdate, data: $input) { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/constants/postgres-error-codes.constants.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/constants/postgres-error-codes.constants.ts new file mode 100644 index 000000000..af0e2fc0e --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/constants/postgres-error-codes.constants.ts @@ -0,0 +1,263 @@ +// From https://www.postgresql.org/docs/current/errcodes-appendix.html +export const POSTGRESQL_ERROR_CODES = [ + '00000', // successful_completion + '01000', // warning + '0100C', // dynamic_result_sets_returned + '01008', // implicit_zero_bit_padding + '01003', // null_value_eliminated_in_set_function + '01007', // privilege_not_granted + '01006', // privilege_not_revoked + '01004', // string_data_right_truncation + '01P01', // deprecated_feature + '02000', // no_data + '02001', // no_additional_dynamic_result_sets_returned + '03000', // sql_statement_not_yet_complete + '08000', // connection_exception + '08003', // connection_does_not_exist + '08006', // connection_failure + '08001', // sqlclient_unable_to_establish_sqlconnection + '08004', // sqlserver_rejected_establishment_of_sqlconnection + '08007', // transaction_resolution_unknown + '08P01', // protocol_violation + '09000', // triggered_action_exception + '0A000', // feature_not_supported + '0B000', // invalid_transaction_initiation + '0F000', // locator_exception + '0F001', // invalid_locator_specification + '0L000', // invalid_grantor + '0LP01', // invalid_grant_operation + '0P000', // invalid_role_specification + '0Z000', // diagnostics_exception + '0Z002', // stacked_diagnostics_accessed_without_active_handler + '20000', // case_not_found + '21000', // cardinality_violation + '22000', // data_exception + '2202E', // array_subscript_error + '22021', // character_not_in_repertoire + '22008', // datetime_field_overflow + '22012', // division_by_zero + '22005', // error_in_assignment + '2200B', // escape_character_conflict + '22022', // indicator_overflow + '22015', // interval_field_overflow + '2201E', // invalid_argument_for_logarithm + '22014', // invalid_argument_for_ntile_function + '22016', // invalid_argument_for_nth_value_function + '2201F', // invalid_argument_for_power_function + '2201G', // invalid_argument_for_width_bucket_function + '22018', // invalid_character_value_for_cast + '22007', // invalid_datetime_format + '22019', // invalid_escape_character + '2200D', // invalid_escape_octet + '22025', // invalid_escape_sequence + '22P06', // nonstandard_use_of_escape_character + '22010', // invalid_indicator_parameter_value + '22023', // invalid_parameter_value + '22013', // invalid_preceding_or_following_size + '2201B', // invalid_regular_expression + '2201W', // invalid_row_count_in_limit_clause + '2201X', // invalid_row_count_in_result_offset_clause + '2202H', // invalid_tablesample_argument + '2202G', // invalid_tablesample_repeat + '22009', // invalid_time_zone_displacement_value + '2200C', // invalid_use_of_escape_character + '2200G', // most_specific_type_mismatch + '22004', // null_value_not_allowed + '22002', // null_value_no_indicator_parameter + '22003', // numeric_value_out_of_range + '2200H', // sequence_generator_limit_exceeded + '22026', // string_data_length_mismatch + '22001', // string_data_right_truncation + '22011', // substring_error + '22027', // trim_error + '22024', // unterminated_c_string + '2200F', // zero_length_character_string + '22P01', // floating_point_exception + '22P02', // invalid_text_representation + '22P03', // invalid_binary_representation + '22P04', // bad_copy_file_format + '22P05', // untranslatable_character + '2200L', // not_an_xml_document + '2200M', // invalid_xml_document + '2200N', // invalid_xml_content + '2200S', // invalid_xml_comment + '2200T', // invalid_xml_processing_instruction + '22030', // duplicate_json_object_key_value + '22031', // invalid_argument_for_sql_json_datetime_function + '22032', // invalid_json_text + '22033', // invalid_sql_json_subscript + '22034', // more_than_one_sql_json_item + '22035', // no_sql_json_item + '22036', // non_numeric_sql_json_item + '22037', // non_unique_keys_in_a_json_object + '22038', // singleton_sql_json_item_required + '22039', // sql_json_array_not_found + '2203A', // sql_json_member_not_found + '2203B', // sql_json_number_not_found + '2203C', // sql_json_object_not_found + '2203D', // too_many_json_array_elements + '2203E', // too_many_json_object_members + '2203F', // sql_json_scalar_required + '2203G', // sql_json_item_cannot_be_cast_to_target_type + '23000', // integrity_constraint_violation + '23001', // restrict_violation + '23502', // not_null_violation + '23503', // foreign_key_violation + '23505', // unique_violation + '23514', // check_violation + '23P01', // exclusion_violation + '24000', // invalid_cursor_state + '25000', // invalid_transaction_state + '25001', // active_sql_transaction + '25002', // branch_transaction_already_active + '25008', // held_cursor_requires_same_isolation_level + '25003', // inappropriate_access_mode_for_branch_transaction + '25004', // inappropriate_isolation_level_for_branch_transaction + '25005', // no_active_sql_transaction_for_branch_transaction + '25006', // read_only_sql_transaction + '25007', // schema_and_data_statement_mixing_not_supported + '25P01', // no_active_sql_transaction + '25P02', // in_failed_sql_transaction + '25P03', // idle_in_transaction_session_timeout + '25P04', // transaction_timeout + '26000', // invalid_sql_statement_name + '27000', // triggered_data_change_violation + '28000', // invalid_authorization_specification + '28P01', // invalid_password + '2B000', // dependent_privilege_descriptors_still_exist + '2BP01', // dependent_objects_still_exist + '2D000', // invalid_transaction_termination + '2F000', // sql_routine_exception + '2F005', // function_executed_no_return_statement + '2F002', // modifying_sql_data_not_permitted + '2F003', // prohibited_sql_statement_attempted + '2F004', // reading_sql_data_not_permitted + '34000', // invalid_cursor_name + '38000', // external_routine_exception + '38001', // containing_sql_not_permitted + '38002', // modifying_sql_data_not_permitted + '38003', // prohibited_sql_statement_attempted + '38004', // reading_sql_data_not_permitted + '39000', // external_routine_invocation_exception + '39001', // invalid_sqlstate_returned + '39004', // null_value_not_allowed + '39P01', // trigger_protocol_violated + '39P02', // srf_protocol_violated + '39P03', // event_trigger_protocol_violated + '3B000', // savepoint_exception + '3B001', // invalid_savepoint_specification + '3D000', // invalid_catalog_name + '3F000', // invalid_schema_name + '40000', // transaction_rollback + '40002', // transaction_integrity_constraint_violation + '40001', // serialization_failure + '40003', // statement_completion_unknown + '40P01', // deadlock_detected + '42000', // syntax_error_or_access_rule_violation + '42601', // syntax_error + '42501', // insufficient_privilege + '42846', // cannot_coerce + '42803', // grouping_error + '42P20', // windowing_error + '42P19', // invalid_recursion + '42830', // invalid_foreign_key + '42602', // invalid_name + '42622', // name_too_long + '42939', // reserved_name + '42804', // datatype_mismatch + '42P18', // indeterminate_datatype + '42P21', // collation_mismatch + '42P22', // indeterminate_collation + '42809', // wrong_object_type + '428C9', // generated_always + '42703', // undefined_column + '42883', // undefined_function + '42P01', // undefined_table + '42P02', // undefined_parameter + '42704', // undefined_object + '42701', // duplicate_column + '42P03', // duplicate_cursor + '42P04', // duplicate_database + '42723', // duplicate_function + '42P05', // duplicate_prepared_statement + '42P06', // duplicate_schema + '42P07', // duplicate_table + '42712', // duplicate_alias + '42710', // duplicate_object + '42702', // ambiguous_column + '42725', // ambiguous_function + '42P08', // ambiguous_parameter + '42P09', // ambiguous_alias + '42P10', // invalid_column_reference + '42611', // invalid_column_definition + '42P11', // invalid_cursor_definition + '42P12', // invalid_database_definition + '42P13', // invalid_function_definition + '42P14', // invalid_prepared_statement_definition + '42P15', // invalid_schema_definition + '42P16', // invalid_table_definition + '42P17', // invalid_object_definition + '44000', // with_check_option_violation + '53000', // insufficient_resources + '53100', // disk_full + '53200', // out_of_memory + '53300', // too_many_connections + '53400', // configuration_limit_exceeded + '54000', // program_limit_exceeded + '54001', // statement_too_complex + '54011', // too_many_columns + '54023', // too_many_arguments + '55000', // object_not_in_prerequisite_state + '55006', // object_in_use + '55P02', // cant_change_runtime_param + '55P03', // lock_not_available + '55P04', // unsafe_new_enum_value_usage + '57000', // operator_intervention + '57014', // query_canceled + '57P01', // admin_shutdown + '57P02', // crash_shutdown + '57P03', // cannot_connect_now + '57P04', // database_dropped + '57P05', // idle_session_timeout + '58000', // system_error + '58030', // io_error + '58P01', // undefined_file + '58P02', // duplicate_file + 'F0000', // config_file_error + 'F0001', // lock_file_exists + 'HV000', // fdw_error + 'HV005', // fdw_column_name_not_found + 'HV002', // fdw_dynamic_parameter_value_needed + 'HV010', // fdw_function_sequence_error + 'HV021', // fdw_inconsistent_descriptor_information + 'HV024', // fdw_invalid_attribute_value + 'HV007', // fdw_invalid_column_name + 'HV008', // fdw_invalid_column_number + 'HV004', // fdw_invalid_data_type + 'HV006', // fdw_invalid_data_type_descriptors + 'HV091', // fdw_invalid_descriptor_field_identifier + 'HV00B', // fdw_invalid_handle + 'HV00C', // fdw_invalid_option_index + 'HV00D', // fdw_invalid_option_name + 'HV090', // fdw_invalid_string_length_or_buffer_length + 'HV00A', // fdw_invalid_string_format + 'HV009', // fdw_invalid_use_of_null_pointer + 'HV014', // fdw_too_many_handles + 'HV001', // fdw_out_of_memory + 'HV00P', // fdw_no_schemas + 'HV00J', // fdw_option_name_not_found + 'HV00K', // fdw_reply_handle + 'HV00Q', // fdw_schema_not_found + 'HV00R', // fdw_table_not_found + 'HV00L', // fdw_unable_to_create_execution + 'HV00M', // fdw_unable_to_create_reply + 'HV00N', // fdw_unable_to_establish_connection + 'P0000', // plpgsql_error + 'P0001', // raise_exception + 'P0002', // no_data_found + 'P0003', // too_many_rows + 'P0004', // assert_failure + 'XX000', // internal_error + 'XX001', // data_corrupted + 'XX002', // index_corrupted +]; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/postgres-exception.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/postgres-exception.ts new file mode 100644 index 000000000..2e53a237b --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/postgres-exception.ts @@ -0,0 +1,7 @@ +export class PostgresException extends Error { + readonly code: string; + constructor(message: string, code: string) { + super(message); + this.code = code; + } +} diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts index 606a7f48d..703ded201 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util.ts @@ -3,8 +3,10 @@ import { QueryFailedError } from 'typeorm'; import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface'; import { GraphqlQueryRunnerException } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; +import { POSTGRESQL_ERROR_CODES } from 'src/engine/api/graphql/workspace-query-runner/constants/postgres-error-codes.constants'; import { graphqlQueryRunnerExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/graphql-query-runner-exception-handler.util'; import { handleDuplicateKeyError } from 'src/engine/api/graphql/workspace-query-runner/utils/handle-duplicate-key-error.util'; +import { PostgresException } from 'src/engine/api/graphql/workspace-query-runner/utils/postgres-exception'; import { workspaceExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-exception-handler.util'; import { WorkspaceQueryRunnerException } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; import { RecordTransformerException } from 'src/engine/core-modules/record-transformer/record-transformer.exception'; @@ -12,8 +14,12 @@ import { recordTransformerGraphqlApiExceptionHandler } from 'src/engine/core-mod import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { permissionGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util'; +interface QueryFailedErrorWithCode extends QueryFailedError { + code: string; +} + export const workspaceQueryRunnerGraphqlApiExceptionHandler = ( - error: Error, + error: QueryFailedErrorWithCode, context: WorkspaceQueryRunnerOptions, ) => { switch (true) { @@ -23,6 +29,11 @@ export const workspaceQueryRunnerGraphqlApiExceptionHandler = ( ) { return handleDuplicateKeyError(error, context); } + const errorCode = (error as QueryFailedErrorWithCode).code; + + if (POSTGRESQL_ERROR_CODES.includes(errorCode)) { + throw new PostgresException(error.message, errorCode); + } throw error; } case error instanceof RecordTransformerException: diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/input/uuid-filter.input-type.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/input/uuid-filter.input-type.ts new file mode 100644 index 000000000..93fb62916 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-types/input/uuid-filter.input-type.ts @@ -0,0 +1,18 @@ +import { GraphQLInputObjectType, GraphQLList } from 'graphql'; + +import { FilterIs } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/filter-is.input-type'; +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; + +export const UUIDFilterType = new GraphQLInputObjectType({ + name: 'UUIDFilter', + fields: { + eq: { type: UUIDScalarType }, + gt: { type: UUIDScalarType }, + gte: { type: UUIDScalarType }, + in: { type: new GraphQLList(UUIDScalarType) }, + lt: { type: UUIDScalarType }, + lte: { type: UUIDScalarType }, + neq: { type: UUIDScalarType }, + is: { type: FilterIs }, + }, +}); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts index e852a272f..370f2d038 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts @@ -27,10 +27,10 @@ import { RawJsonFilterType, StringFilterType, } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input'; -import { IDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/id-filter.input-type'; import { MultiSelectFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/multi-select-filter.input-type'; import { RichTextV2FilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/rich-text.input-type'; import { SelectFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/select-filter.input-type'; +import { UUIDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/uuid-filter.input-type'; import { BigFloatScalarType, UUIDScalarType, @@ -95,14 +95,14 @@ export class TypeMapperService { isIdField?: boolean, ): GraphQLInputObjectType | GraphQLScalarType | undefined { if (isIdField || fieldMetadataType === FieldMetadataType.RELATION) { - return IDFilterType; + return UUIDFilterType; } const typeFilterMapping = new Map< FieldMetadataType, GraphQLInputObjectType | GraphQLScalarType >([ - [FieldMetadataType.UUID, IDFilterType], + [FieldMetadataType.UUID, UUIDFilterType], [FieldMetadataType.TEXT, StringFilterType], [FieldMetadataType.DATE_TIME, DateFilterType], [FieldMetadataType.DATE, DateFilterType], diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts index 3f47f1079..89e07c1c5 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/__tests__/get-resolver-args.spec.ts @@ -1,8 +1,9 @@ -import { GraphQLBoolean, GraphQLID, GraphQLInt, GraphQLString } from 'graphql'; +import { GraphQLBoolean, GraphQLInt, GraphQLString } from 'graphql'; import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory'; +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; import { getResolverArgs } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util'; describe('getResolverArgs', () => { @@ -44,11 +45,11 @@ describe('getResolverArgs', () => { }, }, updateOne: { - id: { type: GraphQLID, isNullable: false }, + id: { type: UUIDScalarType, isNullable: false }, data: { kind: InputTypeDefinitionKind.Update, isNullable: false }, }, deleteOne: { - id: { type: GraphQLID, isNullable: false }, + id: { type: UUIDScalarType, isNullable: false }, }, restoreMany: { filter: { kind: InputTypeDefinitionKind.Filter, isNullable: false }, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts index 421c68346..520435b6a 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/get-resolver-args.util.ts @@ -1,9 +1,10 @@ -import { GraphQLBoolean, GraphQLID, GraphQLInt, GraphQLString } from 'graphql'; +import { GraphQLBoolean, GraphQLInt, GraphQLString } from 'graphql'; import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; import { ArgMetadata } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface'; import { InputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/factories/input-type-definition.factory'; +import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; export const getResolverArgs = ( type: WorkspaceResolverBuilderMethodNames, @@ -77,7 +78,7 @@ export const getResolverArgs = ( case 'updateOne': return { id: { - type: GraphQLID, + type: UUIDScalarType, isNullable: false, }, data: { @@ -88,7 +89,7 @@ export const getResolverArgs = ( case 'findDuplicates': return { ids: { - type: GraphQLID, + type: UUIDScalarType, isNullable: true, isArray: true, }, @@ -101,14 +102,14 @@ export const getResolverArgs = ( case 'deleteOne': return { id: { - type: GraphQLID, + type: UUIDScalarType, isNullable: false, }, }; case 'destroyOne': return { id: { - type: GraphQLID, + type: UUIDScalarType, isNullable: false, }, }; @@ -133,7 +134,7 @@ export const getResolverArgs = ( case 'restoreOne': return { id: { - type: GraphQLID, + type: UUIDScalarType, isNullable: false, }, }; diff --git a/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts b/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts index ddb1875e0..e83526274 100644 --- a/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts @@ -1,7 +1,9 @@ import * as Sentry from '@sentry/node'; +import { isDefined } from 'twenty-shared/utils'; import { ExceptionHandlerOptions } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-options.interface'; +import { PostgresException } from 'src/engine/api/graphql/workspace-query-runner/utils/postgres-exception'; import { ExceptionHandlerDriverInterface } from 'src/engine/core-modules/exception-handler/interfaces'; import { CustomException } from 'src/utils/custom-exception'; @@ -55,7 +57,10 @@ export class ExceptionHandlerSentryDriver }); } - if (exception instanceof CustomException) { + if ( + exception instanceof CustomException && + exception.code !== 'UNKNOWN' + ) { scope.setTag('customExceptionCode', exception.code); scope.setFingerprint([exception.code]); exception.name = exception.code @@ -67,6 +72,19 @@ export class ExceptionHandlerSentryDriver .join(' '); } + if (exception instanceof PostgresException) { + scope.setTag('postgresSqlErrorCode', exception.code); + const fingerPrint = [exception.code]; + const genericOperationName = // truncates to first word: FindOnePerson -> Find, AggregateCompanies -> Aggregate, ... + options?.operation?.name?.match(/^[A-Z][a-z]*/)?.[0]; + + if (isDefined(genericOperationName)) { + fingerPrint.push(genericOperationName); + } + scope.setFingerprint(fingerPrint); + exception.name = exception.message; + } + const eventId = Sentry.captureException(exception, { contexts: { GraphQL: { diff --git a/packages/twenty-server/src/engine/core-modules/search/dtos/object-record-filter-input.ts b/packages/twenty-server/src/engine/core-modules/search/dtos/object-record-filter-input.ts index 54ec70471..feb503455 100644 --- a/packages/twenty-server/src/engine/core-modules/search/dtos/object-record-filter-input.ts +++ b/packages/twenty-server/src/engine/core-modules/search/dtos/object-record-filter-input.ts @@ -1,10 +1,13 @@ -import { Field, ID, InputType, registerEnumType } from '@nestjs/graphql'; +import { Field, InputType, registerEnumType } from '@nestjs/graphql'; import { IsArray, IsOptional } from 'class-validator'; import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; -import { DateScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { + DateScalarType, + UUIDScalarType, +} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; @InputType() export class ObjectRecordFilterInput implements Partial { @@ -22,9 +25,9 @@ export class ObjectRecordFilterInput implements Partial { @IsArray() or?: ObjectRecordFilterInput[]; - @Field(() => IDFilterType, { nullable: true }) + @Field(() => UUIDFilterType, { nullable: true }) @IsOptional() - id?: IDFilterType | null; + id?: UUIDFilterType | null; @Field(() => DateFilterType, { nullable: true }) createdAt?: DateFilterType | null; @@ -36,33 +39,33 @@ export class ObjectRecordFilterInput implements Partial { deletedAt?: DateFilterType | null; } -@InputType('IDFilter') -class IDFilterType { - @Field(() => ID, { nullable: true }) +@InputType('UUIDFilter') +class UUIDFilterType { + @Field(() => UUIDScalarType, { nullable: true }) @IsOptional() eq?: string; - @Field(() => ID, { nullable: true }) + @Field(() => UUIDScalarType, { nullable: true }) @IsOptional() gt?: string; - @Field(() => ID, { nullable: true }) + @Field(() => UUIDScalarType, { nullable: true }) @IsOptional() gte?: string; - @Field(() => [ID], { nullable: true }) + @Field(() => [UUIDScalarType], { nullable: true }) @IsOptional() in?: string[]; - @Field(() => ID, { nullable: true }) + @Field(() => UUIDScalarType, { nullable: true }) @IsOptional() lt?: string; - @Field(() => ID, { nullable: true }) + @Field(() => UUIDScalarType, { nullable: true }) @IsOptional() lte?: string; - @Field(() => ID, { nullable: true }) + @Field(() => UUIDScalarType, { nullable: true }) @IsOptional() neq?: string; diff --git a/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts b/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts index 508aff9fe..83531aede 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts @@ -9,28 +9,34 @@ import { WorkflowTriggerExceptionCode, } from 'src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception'; +export const handleWorkflowTriggerException = ( + exception: WorkflowTriggerException, +) => { + switch (exception.code) { + case WorkflowTriggerExceptionCode.INVALID_INPUT: + case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION: + case WorkflowTriggerExceptionCode.INVALID_ACTION_TYPE: + case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER: + case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_STATUS: + case WorkflowTriggerExceptionCode.FORBIDDEN: + throw new UserInputError(exception.message); + case WorkflowTriggerExceptionCode.NOT_FOUND: + throw new NotFoundError(exception.message); + case WorkflowTriggerExceptionCode.INTERNAL_ERROR: + throw exception; + default: { + const _exhaustiveCheck: never = exception.code; + + throw exception; + } + } +}; + @Catch(WorkflowTriggerException) export class WorkflowTriggerGraphqlApiExceptionFilter implements ExceptionFilter { catch(exception: WorkflowTriggerException) { - switch (exception.code) { - case WorkflowTriggerExceptionCode.INVALID_INPUT: - case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION: - case WorkflowTriggerExceptionCode.INVALID_ACTION_TYPE: - case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER: - case WorkflowTriggerExceptionCode.INVALID_WORKFLOW_STATUS: - case WorkflowTriggerExceptionCode.FORBIDDEN: - throw new UserInputError(exception.message); - case WorkflowTriggerExceptionCode.NOT_FOUND: - throw new NotFoundError(exception.message); - case WorkflowTriggerExceptionCode.INTERNAL_ERROR: - throw exception; - default: { - const _exhaustiveCheck: never = exception.code; - - throw exception; - } - } + handleWorkflowTriggerException(exception); } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts index 09fb65fa2..266508715 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts @@ -1,5 +1,7 @@ import { Injectable } from '@nestjs/common'; +import { isDefined } from 'class-validator'; + import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; @@ -35,7 +37,7 @@ export class MessageImportExceptionHandlerService { ) {} public async handleDriverException( - exception: MessageImportDriverException, + exception: MessageImportDriverException | Error, syncStep: MessageImportSyncStep, messageChannel: Pick< MessageChannelWorkspaceEntity, @@ -43,49 +45,53 @@ export class MessageImportExceptionHandlerService { >, workspaceId: string, ): Promise { - switch (exception.code) { - case MessageImportDriverExceptionCode.NOT_FOUND: - await this.handleNotFoundException( - syncStep, - messageChannel, - workspaceId, - ); - break; - case MessageImportDriverExceptionCode.TEMPORARY_ERROR: - case MessageNetworkExceptionCode.ECONNABORTED: - case MessageNetworkExceptionCode.ENOTFOUND: - case MessageNetworkExceptionCode.ECONNRESET: - case MessageNetworkExceptionCode.ETIMEDOUT: - case MessageNetworkExceptionCode.ERR_NETWORK: - await this.handleTemporaryException( - syncStep, - messageChannel, - workspaceId, - exception, - ); - break; - case MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS: - await this.handleInsufficientPermissionsException( - messageChannel, - workspaceId, - ); - break; - case MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR: - await this.handlePermanentException( - exception, - messageChannel, - workspaceId, - ); - break; - case MessageImportDriverExceptionCode.UNKNOWN: - case MessageImportDriverExceptionCode.UNKNOWN_NETWORK_ERROR: - default: - await this.handleUnknownException( - exception, - messageChannel, - workspaceId, - ); - break; + if (exception instanceof MessageImportDriverException) { + switch (exception.code) { + case MessageImportDriverExceptionCode.NOT_FOUND: + await this.handleNotFoundException( + syncStep, + messageChannel, + workspaceId, + ); + break; + case MessageImportDriverExceptionCode.TEMPORARY_ERROR: + case MessageNetworkExceptionCode.ECONNABORTED: + case MessageNetworkExceptionCode.ENOTFOUND: + case MessageNetworkExceptionCode.ECONNRESET: + case MessageNetworkExceptionCode.ETIMEDOUT: + case MessageNetworkExceptionCode.ERR_NETWORK: + await this.handleTemporaryException( + syncStep, + messageChannel, + workspaceId, + exception, + ); + break; + case MessageImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS: + await this.handleInsufficientPermissionsException( + messageChannel, + workspaceId, + ); + break; + case MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR: + await this.handlePermanentException( + exception, + messageChannel, + workspaceId, + ); + break; + case MessageImportDriverExceptionCode.UNKNOWN: + case MessageImportDriverExceptionCode.UNKNOWN_NETWORK_ERROR: + default: + await this.handleUnknownException( + exception, + messageChannel, + workspaceId, + ); + break; + } + } else { + await this.handleUnknownException(exception, messageChannel, workspaceId); } } @@ -172,7 +178,7 @@ export class MessageImportExceptionHandlerService { } private async handleUnknownException( - exception: MessageImportDriverException, + exception: MessageImportDriverException | Error, messageChannel: Pick, workspaceId: string, ): Promise { @@ -183,7 +189,9 @@ export class MessageImportExceptionHandlerService { ); const messageImportException = new MessageImportException( - exception.message, + isDefined(exception.name) + ? `${exception.name}: ${exception.message}` + : exception.message, MessageImportExceptionCode.UNKNOWN, ); diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job.ts index 6d24cd0d3..816246931 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/jobs/workflow-trigger.job.ts @@ -8,6 +8,7 @@ import { Process } from 'src/engine/core-modules/message-queue/decorators/proces import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; +import { handleWorkflowTriggerException } from 'src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { @@ -106,7 +107,7 @@ export class WorkflowTriggerJob { jobName: WorkflowTriggerJob.name, jobId: data.workflowId, }); - throw e; + handleWorkflowTriggerException(e); } } } diff --git a/packages/twenty-server/test/integration/graphql/utils/delete-one-operation-factory.util.ts b/packages/twenty-server/test/integration/graphql/utils/delete-one-operation-factory.util.ts index 0a7102c76..ea26bc10c 100644 --- a/packages/twenty-server/test/integration/graphql/utils/delete-one-operation-factory.util.ts +++ b/packages/twenty-server/test/integration/graphql/utils/delete-one-operation-factory.util.ts @@ -13,7 +13,7 @@ export const deleteOneOperationFactory = ({ recordId, }: DeleteOneOperationFactoryParams) => ({ query: gql` - mutation Delete${capitalize(objectMetadataSingularName)}($${objectMetadataSingularName}Id: ID!) { + mutation Delete${capitalize(objectMetadataSingularName)}($${objectMetadataSingularName}Id: UUID!) { delete${capitalize(objectMetadataSingularName)}(id: $${objectMetadataSingularName}Id) { ${gqlFields} } diff --git a/packages/twenty-server/test/integration/graphql/utils/destroy-one-operation-factory.util.ts b/packages/twenty-server/test/integration/graphql/utils/destroy-one-operation-factory.util.ts index 38b448bc9..2fafbaca9 100644 --- a/packages/twenty-server/test/integration/graphql/utils/destroy-one-operation-factory.util.ts +++ b/packages/twenty-server/test/integration/graphql/utils/destroy-one-operation-factory.util.ts @@ -13,7 +13,7 @@ export const destroyOneOperationFactory = ({ recordId, }: DestroyOneOperationFactoryParams) => ({ query: gql` - mutation Destroy${capitalize(objectMetadataSingularName)}($${objectMetadataSingularName}Id: ID!) { + mutation Destroy${capitalize(objectMetadataSingularName)}($${objectMetadataSingularName}Id: UUID!) { destroy${capitalize(objectMetadataSingularName)}(id: $${objectMetadataSingularName}Id) { ${gqlFields} } diff --git a/packages/twenty-server/test/integration/graphql/utils/restore-one-operation-factory.util.ts b/packages/twenty-server/test/integration/graphql/utils/restore-one-operation-factory.util.ts index 60282f45f..a824e2085 100644 --- a/packages/twenty-server/test/integration/graphql/utils/restore-one-operation-factory.util.ts +++ b/packages/twenty-server/test/integration/graphql/utils/restore-one-operation-factory.util.ts @@ -13,7 +13,7 @@ export const restoreOneOperationFactory = ({ recordId, }: RestoreOneOperationFactoryParams) => ({ query: gql` - mutation Restore${capitalize(objectMetadataSingularName)}($${objectMetadataSingularName}Id: ID!) { + mutation Restore${capitalize(objectMetadataSingularName)}($${objectMetadataSingularName}Id: UUID!) { restore${capitalize(objectMetadataSingularName)}(id: $${objectMetadataSingularName}Id) { ${gqlFields} } diff --git a/packages/twenty-server/test/integration/graphql/utils/update-one-operation-factory.util.ts b/packages/twenty-server/test/integration/graphql/utils/update-one-operation-factory.util.ts index bf0f8ea0b..dffaec05b 100644 --- a/packages/twenty-server/test/integration/graphql/utils/update-one-operation-factory.util.ts +++ b/packages/twenty-server/test/integration/graphql/utils/update-one-operation-factory.util.ts @@ -15,7 +15,7 @@ export const updateOneOperationFactory = ({ recordId, }: UpdateOneOperationFactoryParams) => ({ query: gql` - mutation Update${capitalize(objectMetadataSingularName)}($${objectMetadataSingularName}Id: ID, $data: ${capitalize(objectMetadataSingularName)}UpdateInput) { + mutation Update${capitalize(objectMetadataSingularName)}($${objectMetadataSingularName}Id: UUID, $data: ${capitalize(objectMetadataSingularName)}UpdateInput) { update${capitalize(objectMetadataSingularName)}(id: $${objectMetadataSingularName}Id, data: $data) { ${gqlFields} }