diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts index 26136f0c68f..22a90429016 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.ts @@ -54,6 +54,7 @@ import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace. import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util'; import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util'; +import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception'; type PermissionOptions = { shouldBypassPermissionChecks?: boolean; @@ -1057,165 +1058,170 @@ export class WorkspaceEntityManager extends EntityManager { | (SaveOptions & { reload: false }), permissionOptions?: PermissionOptions, ): Promise<(T & Entity) | (T & Entity)[] | Entity | Entity[]> { - const permissionOptionsFromArgs = - maybeOptionsOrMaybePermissionOptions && - ('shouldBypassPermissionChecks' in maybeOptionsOrMaybePermissionOptions || - 'objectRecordsPermissions' in maybeOptionsOrMaybePermissionOptions) + try { + const permissionOptionsFromArgs = + maybeOptionsOrMaybePermissionOptions && + ('shouldBypassPermissionChecks' in + maybeOptionsOrMaybePermissionOptions || + 'objectRecordsPermissions' in maybeOptionsOrMaybePermissionOptions) + ? maybeOptionsOrMaybePermissionOptions + : permissionOptions; + + let target = + arguments.length > 1 && + (typeof targetOrEntity === 'function' || + InstanceChecker.isEntitySchema(targetOrEntity) || + typeof targetOrEntity === 'string') + ? targetOrEntity + : undefined; + + const entity = target ? entityOrMaybeOptions : targetOrEntity; + + const options = target ? maybeOptionsOrMaybePermissionOptions - : permissionOptions; + : entityOrMaybeOptions; - let target = - arguments.length > 1 && - (typeof targetOrEntity === 'function' || - InstanceChecker.isEntitySchema(targetOrEntity) || - typeof targetOrEntity === 'string') - ? targetOrEntity - : undefined; + if (InstanceChecker.isEntitySchema(target)) target = target.options.name; + if (Array.isArray(entity) && entity.length === 0) + return Promise.resolve(entity as Entity[]); - const entity = target ? entityOrMaybeOptions : targetOrEntity; + const queryRunnerForEntityPersistExecutor = + this.connection.createQueryRunnerForEntityPersistExecutor(); - const options = target - ? maybeOptionsOrMaybePermissionOptions - : entityOrMaybeOptions; + const isEntityArray = Array.isArray(entity); + const entityTarget = + target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor); - if (InstanceChecker.isEntitySchema(target)) target = target.options.name; - if (Array.isArray(entity) && entity.length === 0) - return Promise.resolve(entity as Entity[]); + const entityArray = isEntityArray ? entity : [entity]; - const queryRunnerForEntityPersistExecutor = - this.connection.createQueryRunnerForEntityPersistExecutor(); - - const isEntityArray = Array.isArray(entity); - const entityTarget = - target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor); - - const entityArray = isEntityArray ? entity : [entity]; - - const relationNestedQueries = new RelationNestedQueries( - this.internalContext, - ); - - const relationNestedConfig = - relationNestedQueries.prepareNestedRelationQueries( - entityArray, - entityTarget, + const relationNestedQueries = new RelationNestedQueries( + this.internalContext, ); - const entityWithConnectedRelations = isDefined(relationNestedConfig) - ? await relationNestedQueries.processRelationNestedQueries({ - entities: entityArray, - relationNestedConfig, - queryBuilder: this.createQueryBuilder( - undefined, - undefined, - undefined, - permissionOptions, - ), - }) - : entityArray; + const relationNestedConfig = + relationNestedQueries.prepareNestedRelationQueries( + entityArray, + entityTarget, + ); - const entityIds = entityArray - .map((entity) => (entity as { id: string }).id) - .filter(isDefined); - const beforeUpdate = await this.find( - entityTarget, - { - where: { id: In(entityIds) }, - }, - { shouldBypassPermissionChecks: true }, // Bypass as this is for event emission - ); + const entityWithConnectedRelations = isDefined(relationNestedConfig) + ? await relationNestedQueries.processRelationNestedQueries({ + entities: entityArray, + relationNestedConfig, + queryBuilder: this.createQueryBuilder( + undefined, + undefined, + undefined, + permissionOptions, + ), + }) + : entityArray; - const beforeUpdateMapById = beforeUpdate.reduce( - (acc, e: ObjectLiteral) => { - acc[e.id] = e; + const entityIds = entityArray + .map((entity) => (entity as { id: string }).id) + .filter(isDefined); + const beforeUpdate = await this.find( + entityTarget, + { + where: { id: In(entityIds) }, + }, + { shouldBypassPermissionChecks: true }, // Bypass as this is for event emission + ); - return acc; - }, - {} as Record, - ); + const beforeUpdateMapById = beforeUpdate.reduce( + (acc, e: ObjectLiteral) => { + acc[e.id] = e; - const objectMetadataItem = getObjectMetadataFromEntityTarget( - entityTarget, - this.internalContext, - ); + return acc; + }, + {} as Record, + ); - const formattedEntityOrEntities = formatData( - entityWithConnectedRelations, - objectMetadataItem, - ); + const objectMetadataItem = getObjectMetadataFromEntityTarget( + entityTarget, + this.internalContext, + ); - const updatedColumns = formattedEntityOrEntities - .map((e) => Object.keys(e)) - .flat(); - - this.validatePermissions({ - target: targetOrEntity, - operationType: 'update', - permissionOptions: permissionOptionsFromArgs, - selectedColumns: [], - updatedColumns, - }); - - const result = await new EntityPersistExecutor( - this.connection, - queryRunnerForEntityPersistExecutor, - 'save', - target, - formattedEntityOrEntities as ObjectLiteral[], - options as SaveOptions | (SaveOptions & { reload: false }), - ) - .execute() - .then(() => formattedEntityOrEntities as Entity[]) - .finally(() => queryRunnerForEntityPersistExecutor.release()); - - const resultArray = Array.isArray(result) ? result : [result]; - - let formattedResult = formatResult( - resultArray, - objectMetadataItem, - this.internalContext.objectMetadataMaps, - ); - - const updatedEntities = formattedResult.filter( - (entity) => beforeUpdateMapById[entity.id], - ); - const createdEntities = formattedResult.filter( - (entity) => !beforeUpdateMapById[entity.id], - ); - - await this.internalContext.eventEmitterService.emitMutationEvent({ - action: DatabaseEventAction.UPDATED, - objectMetadataItem, - workspaceId: this.internalContext.workspaceId, - entities: updatedEntities, - beforeEntities: updatedEntities.map( - (entity) => beforeUpdateMapById[entity.id], - ), - }); - - await this.internalContext.eventEmitterService.emitMutationEvent({ - action: DatabaseEventAction.CREATED, - objectMetadataItem, - workspaceId: this.internalContext.workspaceId, - entities: createdEntities, - }); - - const isFieldPermissionsEnabled = - this.getFeatureFlagMap().IS_FIELDS_PERMISSIONS_ENABLED; - - const permissionCheckApplies = - permissionOptionsFromArgs?.shouldBypassPermissionChecks !== true && - objectMetadataItem.isSystem !== true; - - if (isFieldPermissionsEnabled && permissionCheckApplies) { - formattedResult = this.getFormattedResultWithoutNonReadableFields({ - formattedResult, + const formattedEntityOrEntities = formatData( + entityWithConnectedRelations, objectMetadataItem, - permissionOptionsFromArgs, - }); - } + ); - return isEntityArray ? formattedResult : formattedResult[0]; + const updatedColumns = formattedEntityOrEntities + .map((e) => Object.keys(e)) + .flat(); + + this.validatePermissions({ + target: targetOrEntity, + operationType: 'update', + permissionOptions: permissionOptionsFromArgs, + selectedColumns: [], + updatedColumns, + }); + + const result = await new EntityPersistExecutor( + this.connection, + queryRunnerForEntityPersistExecutor, + 'save', + target, + formattedEntityOrEntities as ObjectLiteral[], + options as SaveOptions | (SaveOptions & { reload: false }), + ) + .execute() + .then(() => formattedEntityOrEntities as Entity[]) + .finally(() => queryRunnerForEntityPersistExecutor.release()); + + const resultArray = Array.isArray(result) ? result : [result]; + + let formattedResult = formatResult( + resultArray, + objectMetadataItem, + this.internalContext.objectMetadataMaps, + ); + + const updatedEntities = formattedResult.filter( + (entity) => beforeUpdateMapById[entity.id], + ); + const createdEntities = formattedResult.filter( + (entity) => !beforeUpdateMapById[entity.id], + ); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.UPDATED, + objectMetadataItem, + workspaceId: this.internalContext.workspaceId, + entities: updatedEntities, + beforeEntities: updatedEntities.map( + (entity) => beforeUpdateMapById[entity.id], + ), + }); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.CREATED, + objectMetadataItem, + workspaceId: this.internalContext.workspaceId, + entities: createdEntities, + }); + + const isFieldPermissionsEnabled = + this.getFeatureFlagMap().IS_FIELDS_PERMISSIONS_ENABLED; + + const permissionCheckApplies = + permissionOptionsFromArgs?.shouldBypassPermissionChecks !== true && + objectMetadataItem.isSystem !== true; + + if (isFieldPermissionsEnabled && permissionCheckApplies) { + formattedResult = this.getFormattedResultWithoutNonReadableFields({ + formattedResult, + objectMetadataItem, + permissionOptionsFromArgs, + }); + } + + return isEntityArray ? formattedResult : formattedResult[0]; + } catch (error) { + throw computeTwentyORMException(error); + } } private getFormattedResultWithoutNonReadableFields< diff --git a/packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts b/packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts new file mode 100644 index 00000000000..c5c22d34623 --- /dev/null +++ b/packages/twenty-server/src/engine/twenty-orm/error-handling/compute-twenty-orm-exception.ts @@ -0,0 +1,23 @@ +import { t } from '@lingui/core/macro'; +import { QueryFailedError } from 'typeorm'; + +import { + TwentyORMException, + TwentyORMExceptionCode, +} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; + +export const computeTwentyORMException = (error: Error) => { + if (error instanceof QueryFailedError) { + if (error.message.includes('Query read timeout')) { + return new TwentyORMException( + 'Query read timeout', + TwentyORMExceptionCode.QUERY_READ_TIMEOUT, + { + userFriendlyMessage: t`We are experiencing a temporary issue with our database. Please try again later.`, + }, + ); + } + } + + return error; +}; diff --git a/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts b/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts index 0b92c068231..f62885e476d 100644 --- a/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts +++ b/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts @@ -17,4 +17,5 @@ export enum TwentyORMExceptionCode { MISSING_MAIN_ALIAS_TARGET = 'MISSING_MAIN_ALIAS_TARGET', METHOD_NOT_ALLOWED = 'METHOD_NOT_ALLOWED', ENUM_TYPE_NAME_NOT_FOUND = 'ENUM_TYPE_NAME_NOT_FOUND', + QUERY_READ_TIMEOUT = 'QUERY_READ_TIMEOUT', } diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts index d0b241b4e57..67dbe467ad8 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-delete-query-builder.ts @@ -14,6 +14,7 @@ import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/works import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception'; import { TwentyORMException, TwentyORMExceptionCode, @@ -62,64 +63,69 @@ export class WorkspaceDeleteQueryBuilder< } override async execute(): Promise { - validateQueryIsPermittedOrThrow({ - expressionMap: this.expressionMap, - objectRecordsPermissions: this.objectRecordsPermissions, - objectMetadataMaps: this.internalContext.objectMetadataMaps, - shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, - isFieldPermissionsEnabled: - this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED], - }); + try { + validateQueryIsPermittedOrThrow({ + expressionMap: this.expressionMap, + objectRecordsPermissions: this.objectRecordsPermissions, + objectMetadataMaps: this.internalContext.objectMetadataMaps, + shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, + isFieldPermissionsEnabled: + this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED], + }); - const mainAliasTarget = this.getMainAliasTarget(); + const mainAliasTarget = this.getMainAliasTarget(); - const objectMetadata = getObjectMetadataFromEntityTarget( - mainAliasTarget, - this.internalContext, - ); + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); - const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder( - this as unknown as WorkspaceSelectQueryBuilder, - this.objectRecordsPermissions, - this.internalContext, - true, - this.authContext, - this.featureFlagMap, - ); + const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder( + this as unknown as WorkspaceSelectQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + true, + this.authContext, + this.featureFlagMap, + ); - eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres; - eventSelectQueryBuilder.expressionMap.aliases = this.expressionMap.aliases; - eventSelectQueryBuilder.setParameters(this.getParameters()); + eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres; + eventSelectQueryBuilder.expressionMap.aliases = + this.expressionMap.aliases; + eventSelectQueryBuilder.setParameters(this.getParameters()); - const before = await eventSelectQueryBuilder.getOne(); + const before = await eventSelectQueryBuilder.getOne(); - const result = await super.execute(); + const result = await super.execute(); - const formattedResult = formatResult( - result.raw, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); + const formattedResult = formatResult( + result.raw, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); - const formattedBefore = formatResult( - before, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); + const formattedBefore = formatResult( + before, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); - await this.internalContext.eventEmitterService.emitMutationEvent({ - action: DatabaseEventAction.DESTROYED, - objectMetadataItem: objectMetadata, - workspaceId: this.internalContext.workspaceId, - entities: formattedBefore, - authContext: this.authContext, - }); + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.DESTROYED, + objectMetadataItem: objectMetadata, + workspaceId: this.internalContext.workspaceId, + entities: formattedBefore, + authContext: this.authContext, + }); - return { - raw: result.raw, - generatedMaps: formattedResult, - affected: result.affected, - }; + return { + raw: result.raw, + generatedMaps: formattedResult, + affected: result.affected, + }; + } catch (error) { + throw computeTwentyORMException(error); + } } private getMainAliasTarget(): EntityTarget { diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts index 5a15c40e10c..d447e429aef 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-insert-query-builder.ts @@ -17,6 +17,7 @@ import { QueryDeepPartialEntityWithNestedRelationFields } from 'src/engine/twent import { RelationConnectQueryConfig } from 'src/engine/twenty-orm/entity-manager/types/relation-connect-query-config.type'; import { RelationDisconnectQueryFieldsByEntityIndex } from 'src/engine/twenty-orm/entity-manager/types/relation-nested-query-fields-by-entity-index.type'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager'; +import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception'; import { TwentyORMException, TwentyORMExceptionCode, @@ -100,98 +101,102 @@ export class WorkspaceInsertQueryBuilder< } override async execute(): Promise { - validateQueryIsPermittedOrThrow({ - expressionMap: this.expressionMap, - objectRecordsPermissions: this.objectRecordsPermissions, - objectMetadataMaps: this.internalContext.objectMetadataMaps, - shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, - isFieldPermissionsEnabled: - this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED], - }); + try { + validateQueryIsPermittedOrThrow({ + expressionMap: this.expressionMap, + objectRecordsPermissions: this.objectRecordsPermissions, + objectMetadataMaps: this.internalContext.objectMetadataMaps, + shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, + isFieldPermissionsEnabled: + this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED], + }); - const mainAliasTarget = this.getMainAliasTarget(); + const mainAliasTarget = this.getMainAliasTarget(); - const objectMetadata = getObjectMetadataFromEntityTarget( - mainAliasTarget, - this.internalContext, - ); - - if (isDefined(this.relationNestedConfig)) { - const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder( - this as unknown as WorkspaceSelectQueryBuilder, - this.objectRecordsPermissions, + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, this.internalContext, - this.shouldBypassPermissionChecks, - this.authContext, ); - const updatedValues = - await this.relationNestedQueries.processRelationNestedQueries({ - entities: this.expressionMap.valuesSet as - | QueryDeepPartialEntityWithNestedRelationFields - | QueryDeepPartialEntityWithNestedRelationFields[], - relationNestedConfig: this.relationNestedConfig, - queryBuilder: nestedRelationQueryBuilder, - }); + if (isDefined(this.relationNestedConfig)) { + const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder( + this as unknown as WorkspaceSelectQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + this.shouldBypassPermissionChecks, + this.authContext, + ); - this.expressionMap.valuesSet = updatedValues; + const updatedValues = + await this.relationNestedQueries.processRelationNestedQueries({ + entities: this.expressionMap.valuesSet as + | QueryDeepPartialEntityWithNestedRelationFields + | QueryDeepPartialEntityWithNestedRelationFields[], + relationNestedConfig: this.relationNestedConfig, + queryBuilder: nestedRelationQueryBuilder, + }); + + this.expressionMap.valuesSet = updatedValues; + } + + const result = await super.execute(); + const eventSelectQueryBuilder = ( + this.connection.manager as WorkspaceEntityManager + ).createQueryBuilder( + mainAliasTarget, + this.expressionMap.mainAlias?.metadata.name ?? '', + undefined, + { + shouldBypassPermissionChecks: true, + }, + ) as WorkspaceSelectQueryBuilder; + + eventSelectQueryBuilder.whereInIds( + result.identifiers.map((identifier) => identifier.id), + ); + + const afterResult = await eventSelectQueryBuilder.getMany(); + + const formattedResultForEvent = formatResult( + afterResult, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.CREATED, + objectMetadataItem: objectMetadata, + workspaceId: this.internalContext.workspaceId, + entities: formattedResultForEvent, + authContext: this.authContext, + }); + + // TypeORM returns all entity columns for insertions + const resultWithoutInsertionExtraColumns = result.raw.map( + (rawResult: Record) => + Object.keys(rawResult) + .filter((key) => this.expressionMap.returning.includes(key)) + .reduce((filtered: Record, key) => { + filtered[key] = rawResult[key]; + + return filtered; + }, {}), + ); + + const formattedResult = formatResult( + resultWithoutInsertionExtraColumns, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + return { + raw: resultWithoutInsertionExtraColumns, + generatedMaps: formattedResult, + identifiers: result.identifiers, + }; + } catch (error) { + throw computeTwentyORMException(error); } - - const result = await super.execute(); - const eventSelectQueryBuilder = ( - this.connection.manager as WorkspaceEntityManager - ).createQueryBuilder( - mainAliasTarget, - this.expressionMap.mainAlias?.metadata.name ?? '', - undefined, - { - shouldBypassPermissionChecks: true, - }, - ) as WorkspaceSelectQueryBuilder; - - eventSelectQueryBuilder.whereInIds( - result.identifiers.map((identifier) => identifier.id), - ); - - const afterResult = await eventSelectQueryBuilder.getMany(); - - const formattedResultForEvent = formatResult( - afterResult, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); - - await this.internalContext.eventEmitterService.emitMutationEvent({ - action: DatabaseEventAction.CREATED, - objectMetadataItem: objectMetadata, - workspaceId: this.internalContext.workspaceId, - entities: formattedResultForEvent, - authContext: this.authContext, - }); - - // TypeORM returns all entity columns for insertions - const resultWithoutInsertionExtraColumns = result.raw.map( - (rawResult: Record) => - Object.keys(rawResult) - .filter((key) => this.expressionMap.returning.includes(key)) - .reduce((filtered: Record, key) => { - filtered[key] = rawResult[key]; - - return filtered; - }, {}), - ); - - const formattedResult = formatResult( - resultWithoutInsertionExtraColumns, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); - - return { - raw: resultWithoutInsertionExtraColumns, - generatedMaps: formattedResult, - identifiers: result.identifiers, - }; } private getMainAliasTarget(): EntityTarget { diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts index 6e920dcf6c7..aba23753b90 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-select-query-builder.ts @@ -11,6 +11,7 @@ import { PermissionsException, PermissionsExceptionCode, } from 'src/engine/metadata-modules/permissions/permissions.exception'; +import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception'; import { TwentyORMException, TwentyORMExceptionCode, @@ -66,111 +67,139 @@ export class WorkspaceSelectQueryBuilder< // eslint-disable-next-line @typescript-eslint/no-explicit-any override async execute(): Promise { - this.validatePermissions(); + try { + this.validatePermissions(); - const mainAliasTarget = this.getMainAliasTarget(); + const mainAliasTarget = this.getMainAliasTarget(); - const objectMetadata = getObjectMetadataFromEntityTarget( - mainAliasTarget, - this.internalContext, - ); + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); - const result = await super.execute(); + const result = await super.execute(); - const formattedResult = formatResult( - result, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); + const formattedResult = formatResult( + result, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); - return { - raw: result, - generatedMaps: formattedResult, - identifiers: result.identifiers, - }; + return { + raw: result, + generatedMaps: formattedResult, + identifiers: result.identifiers, + }; + } catch (error) { + throw computeTwentyORMException(error); + } } override async getMany(): Promise { - this.validatePermissions(); + try { + this.validatePermissions(); - const mainAliasTarget = this.getMainAliasTarget(); + const mainAliasTarget = this.getMainAliasTarget(); - const objectMetadata = getObjectMetadataFromEntityTarget( - mainAliasTarget, - this.internalContext, - ); + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); - const result = await super.getMany(); + const result = await super.getMany(); - const formattedResult = formatResult( - result, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); + const formattedResult = formatResult( + result, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); - return formattedResult; + return formattedResult; + } catch (error) { + throw computeTwentyORMException(error); + } } // eslint-disable-next-line @typescript-eslint/no-explicit-any override getRawOne(): Promise { - this.validatePermissions(); + try { + this.validatePermissions(); - return super.getRawOne(); + return super.getRawOne(); + } catch (error) { + throw computeTwentyORMException(error); + } } // eslint-disable-next-line @typescript-eslint/no-explicit-any override getRawMany(): Promise { - this.validatePermissions(); + try { + this.validatePermissions(); - return super.getRawMany(); + return super.getRawMany(); + } catch (error) { + throw computeTwentyORMException(error); + } } override async getOne(): Promise { - this.validatePermissions(); + try { + this.validatePermissions(); - const mainAliasTarget = this.getMainAliasTarget(); + const mainAliasTarget = this.getMainAliasTarget(); - const objectMetadata = getObjectMetadataFromEntityTarget( - mainAliasTarget, - this.internalContext, - ); + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); - const result = await super.getOne(); + const result = await super.getOne(); - const formattedResult = formatResult( - result, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); + const formattedResult = formatResult( + result, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); - return formattedResult; + return formattedResult; + } catch (error) { + throw computeTwentyORMException(error); + } } override async getOneOrFail(): Promise { - this.validatePermissions(); + try { + this.validatePermissions(); - const mainAliasTarget = this.getMainAliasTarget(); + const mainAliasTarget = this.getMainAliasTarget(); - const objectMetadata = getObjectMetadataFromEntityTarget( - mainAliasTarget, - this.internalContext, - ); + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); - const result = await super.getOneOrFail(); + const result = await super.getOneOrFail(); - const formattedResult = formatResult( - result, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); + const formattedResult = formatResult( + result, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); - return formattedResult[0]; + return formattedResult[0]; + } catch (error) { + throw computeTwentyORMException(error); + } } override getCount(): Promise { - this.validatePermissions(); + try { + this.validatePermissions(); - return super.getCount(); + return super.getCount(); + } catch (error) { + throw computeTwentyORMException(error); + } } override getExists(): Promise { @@ -181,24 +210,28 @@ export class WorkspaceSelectQueryBuilder< } override async getManyAndCount(): Promise<[T[], number]> { - this.validatePermissions(); + try { + this.validatePermissions(); - const mainAliasTarget = this.getMainAliasTarget(); + const mainAliasTarget = this.getMainAliasTarget(); - const objectMetadata = getObjectMetadataFromEntityTarget( - mainAliasTarget, - this.internalContext, - ); + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); - const [result, count] = await super.getManyAndCount(); + const [result, count] = await super.getManyAndCount(); - const formattedResult = formatResult( - result, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); + const formattedResult = formatResult( + result, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); - return [formattedResult, count]; + return [formattedResult, count]; + } catch (error) { + throw computeTwentyORMException(error); + } } override insert(): WorkspaceInsertQueryBuilder { diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-soft-delete-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-soft-delete-query-builder.ts index efd5e876261..c3b4a0c83a5 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-soft-delete-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-soft-delete-query-builder.ts @@ -13,6 +13,7 @@ import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/works import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception'; import { TwentyORMException, TwentyORMExceptionCode, @@ -62,43 +63,47 @@ export class WorkspaceSoftDeleteQueryBuilder< } override async execute(): Promise { - validateQueryIsPermittedOrThrow({ - expressionMap: this.expressionMap, - objectRecordsPermissions: this.objectRecordsPermissions, - objectMetadataMaps: this.internalContext.objectMetadataMaps, - shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, - isFieldPermissionsEnabled: - this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED], - }); + try { + validateQueryIsPermittedOrThrow({ + expressionMap: this.expressionMap, + objectRecordsPermissions: this.objectRecordsPermissions, + objectMetadataMaps: this.internalContext.objectMetadataMaps, + shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, + isFieldPermissionsEnabled: + this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED], + }); - const mainAliasTarget = this.getMainAliasTarget(); + const mainAliasTarget = this.getMainAliasTarget(); - const objectMetadata = getObjectMetadataFromEntityTarget( - mainAliasTarget, - this.internalContext, - ); + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); - const after = await super.execute(); + const after = await super.execute(); - const formattedAfter = formatResult( - after.raw, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); + const formattedAfter = formatResult( + after.raw, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); - await this.internalContext.eventEmitterService.emitMutationEvent({ - action: DatabaseEventAction.DELETED, - objectMetadataItem: objectMetadata, - workspaceId: this.internalContext.workspaceId, - entities: formattedAfter, - authContext: this.authContext, - }); + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.DELETED, + objectMetadataItem: objectMetadata, + workspaceId: this.internalContext.workspaceId, + entities: formattedAfter, + authContext: this.authContext, + }); - return { - raw: after.raw, - generatedMaps: formattedAfter, - affected: after.affected, - }; + return { + raw: after.raw, + generatedMaps: formattedAfter, + affected: after.affected, + }; + } catch (error) { + throw computeTwentyORMException(error); + } } override select(): WorkspaceSelectQueryBuilder { diff --git a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts index 5b3595ddc26..67a9129c89a 100644 --- a/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts +++ b/packages/twenty-server/src/engine/twenty-orm/repository/workspace-update-query-builder.ts @@ -17,6 +17,7 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu import { QueryDeepPartialEntityWithNestedRelationFields } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-nested-relation-fields.type'; import { RelationConnectQueryConfig } from 'src/engine/twenty-orm/entity-manager/types/relation-connect-query-config.type'; import { RelationDisconnectQueryFieldsByEntityIndex } from 'src/engine/twenty-orm/entity-manager/types/relation-nested-query-fields-by-entity-index.type'; +import { computeTwentyORMException } from 'src/engine/twenty-orm/error-handling/compute-twenty-orm-exception'; import { TwentyORMException, TwentyORMExceptionCode, @@ -80,156 +81,42 @@ export class WorkspaceUpdateQueryBuilder< } override async execute(): Promise { - if (this.manyInputs) { - return this.executeMany(); - } - - validateQueryIsPermittedOrThrow({ - expressionMap: this.expressionMap, - objectRecordsPermissions: this.objectRecordsPermissions, - objectMetadataMaps: this.internalContext.objectMetadataMaps, - shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, - isFieldPermissionsEnabled: - this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED], - }); - - const mainAliasTarget = this.getMainAliasTarget(); - - const objectMetadata = getObjectMetadataFromEntityTarget( - mainAliasTarget, - this.internalContext, - ); - - const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder( - this as unknown as WorkspaceSelectQueryBuilder, - this.objectRecordsPermissions, - this.internalContext, - true, - this.authContext, - this.featureFlagMap, - ); - - eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres; - eventSelectQueryBuilder.expressionMap.aliases = this.expressionMap.aliases; - eventSelectQueryBuilder.setParameters(this.getParameters()); - - const before = await eventSelectQueryBuilder.getMany(); - - const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder( - this as unknown as WorkspaceSelectQueryBuilder, - this.objectRecordsPermissions, - this.internalContext, - this.shouldBypassPermissionChecks, - this.authContext, - ); - - if (isDefined(this.relationNestedConfig)) { - const updatedValues = - await this.relationNestedQueries.processRelationNestedQueries({ - entities: this.expressionMap.valuesSet as - | QueryDeepPartialEntityWithNestedRelationFields - | QueryDeepPartialEntityWithNestedRelationFields[], - relationNestedConfig: this.relationNestedConfig, - queryBuilder: nestedRelationQueryBuilder, - }); - - this.expressionMap.valuesSet = - updatedValues.length === 1 ? updatedValues[0] : updatedValues; - } - - const formattedBefore = formatResult( - before, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); - - const result = await super.execute(); - const after = await eventSelectQueryBuilder.getMany(); - - const formattedAfter = formatResult( - after, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); - - await this.internalContext.eventEmitterService.emitMutationEvent({ - action: DatabaseEventAction.UPDATED, - objectMetadataItem: objectMetadata, - workspaceId: this.internalContext.workspaceId, - entities: formattedAfter, - beforeEntities: formattedBefore, - authContext: this.authContext, - }); - - const formattedResult = formatResult( - result.raw, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); - - return { - raw: result.raw, - generatedMaps: formattedResult, - affected: result.affected, - }; - } - - public async executeMany(): Promise { - for (const input of this.manyInputs) { - const fakeExpressionMapToValidatePermissions = Object.assign( - {}, - this.expressionMap, - { - wheres: input.criteria, - valuesSet: input.partialEntity, - }, - ); + try { + if (this.manyInputs) { + return this.executeMany(); + } validateQueryIsPermittedOrThrow({ - expressionMap: fakeExpressionMapToValidatePermissions, + expressionMap: this.expressionMap, objectRecordsPermissions: this.objectRecordsPermissions, objectMetadataMaps: this.internalContext.objectMetadataMaps, shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, isFieldPermissionsEnabled: this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED], }); - } - const mainAliasTarget = this.getMainAliasTarget(); + const mainAliasTarget = this.getMainAliasTarget(); - const objectMetadata = getObjectMetadataFromEntityTarget( - mainAliasTarget, - this.internalContext, - ); + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); - const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder( - this as unknown as WorkspaceSelectQueryBuilder, - this.objectRecordsPermissions, - this.internalContext, - true, - this.authContext, - this.featureFlagMap, - ); + const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder( + this as unknown as WorkspaceSelectQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + true, + this.authContext, + this.featureFlagMap, + ); - eventSelectQueryBuilder.whereInIds( - this.manyInputs.map((input) => input.criteria), - ); - eventSelectQueryBuilder.expressionMap.aliases = this.expressionMap.aliases; - eventSelectQueryBuilder.setParameters(this.getParameters()); + eventSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres; + eventSelectQueryBuilder.expressionMap.aliases = + this.expressionMap.aliases; + eventSelectQueryBuilder.setParameters(this.getParameters()); - const beforeRecords = await eventSelectQueryBuilder.getMany(); - - const formattedBefore = formatResult( - beforeRecords, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); - - const results: UpdateResult[] = []; - - for (const input of this.manyInputs) { - this.expressionMap.valuesSet = input.partialEntity; - this.where({ id: input.criteria }); + const before = await eventSelectQueryBuilder.getMany(); const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder( this as unknown as WorkspaceSelectQueryBuilder, @@ -242,7 +129,7 @@ export class WorkspaceUpdateQueryBuilder< if (isDefined(this.relationNestedConfig)) { const updatedValues = await this.relationNestedQueries.processRelationNestedQueries({ - entities: input.partialEntity as + entities: this.expressionMap.valuesSet as | QueryDeepPartialEntityWithNestedRelationFields | QueryDeepPartialEntityWithNestedRelationFields[], relationNestedConfig: this.relationNestedConfig, @@ -253,39 +140,163 @@ export class WorkspaceUpdateQueryBuilder< updatedValues.length === 1 ? updatedValues[0] : updatedValues; } + const formattedBefore = formatResult( + before, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + const result = await super.execute(); + const after = await eventSelectQueryBuilder.getMany(); - results.push(result); + const formattedAfter = formatResult( + after, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.UPDATED, + objectMetadataItem: objectMetadata, + workspaceId: this.internalContext.workspaceId, + entities: formattedAfter, + beforeEntities: formattedBefore, + authContext: this.authContext, + }); + + const formattedResult = formatResult( + result.raw, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + return { + raw: result.raw, + generatedMaps: formattedResult, + affected: result.affected, + }; + } catch (error) { + throw computeTwentyORMException(error); } + } - const afterRecords = await eventSelectQueryBuilder.getMany(); + public async executeMany(): Promise { + try { + for (const input of this.manyInputs) { + const fakeExpressionMapToValidatePermissions = Object.assign( + {}, + this.expressionMap, + { + wheres: input.criteria, + valuesSet: input.partialEntity, + }, + ); - const formattedAfter = formatResult( - afterRecords, - objectMetadata, - this.internalContext.objectMetadataMaps, - ); + validateQueryIsPermittedOrThrow({ + expressionMap: fakeExpressionMapToValidatePermissions, + objectRecordsPermissions: this.objectRecordsPermissions, + objectMetadataMaps: this.internalContext.objectMetadataMaps, + shouldBypassPermissionChecks: this.shouldBypassPermissionChecks, + isFieldPermissionsEnabled: + this.featureFlagMap?.[FeatureFlagKey.IS_FIELDS_PERMISSIONS_ENABLED], + }); + } - await this.internalContext.eventEmitterService.emitMutationEvent({ - action: DatabaseEventAction.UPDATED, - objectMetadataItem: objectMetadata, - workspaceId: this.internalContext.workspaceId, - entities: formattedAfter, - beforeEntities: formattedBefore, - authContext: this.authContext, - }); + const mainAliasTarget = this.getMainAliasTarget(); - const formattedResults = formatResult( - results.map((result) => result.raw), - objectMetadata, - this.internalContext.objectMetadataMaps, - ); + const objectMetadata = getObjectMetadataFromEntityTarget( + mainAliasTarget, + this.internalContext, + ); - return { - raw: results.map((result) => result.raw), - generatedMaps: formattedResults, - affected: results.length, - }; + const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder( + this as unknown as WorkspaceSelectQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + true, + this.authContext, + this.featureFlagMap, + ); + + eventSelectQueryBuilder.whereInIds( + this.manyInputs.map((input) => input.criteria), + ); + eventSelectQueryBuilder.expressionMap.aliases = + this.expressionMap.aliases; + eventSelectQueryBuilder.setParameters(this.getParameters()); + + const beforeRecords = await eventSelectQueryBuilder.getMany(); + + const formattedBefore = formatResult( + beforeRecords, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + const results: UpdateResult[] = []; + + for (const input of this.manyInputs) { + this.expressionMap.valuesSet = input.partialEntity; + this.where({ id: input.criteria }); + + const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder( + this as unknown as WorkspaceSelectQueryBuilder, + this.objectRecordsPermissions, + this.internalContext, + this.shouldBypassPermissionChecks, + this.authContext, + ); + + if (isDefined(this.relationNestedConfig)) { + const updatedValues = + await this.relationNestedQueries.processRelationNestedQueries({ + entities: input.partialEntity as + | QueryDeepPartialEntityWithNestedRelationFields + | QueryDeepPartialEntityWithNestedRelationFields[], + relationNestedConfig: this.relationNestedConfig, + queryBuilder: nestedRelationQueryBuilder, + }); + + this.expressionMap.valuesSet = + updatedValues.length === 1 ? updatedValues[0] : updatedValues; + } + + const result = await super.execute(); + + results.push(result); + } + + const afterRecords = await eventSelectQueryBuilder.getMany(); + + const formattedAfter = formatResult( + afterRecords, + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + await this.internalContext.eventEmitterService.emitMutationEvent({ + action: DatabaseEventAction.UPDATED, + objectMetadataItem: objectMetadata, + workspaceId: this.internalContext.workspaceId, + entities: formattedAfter, + beforeEntities: formattedBefore, + authContext: this.authContext, + }); + + const formattedResults = formatResult( + results.map((result) => result.raw), + objectMetadata, + this.internalContext.objectMetadataMaps, + ); + + return { + raw: results.map((result) => result.raw), + generatedMaps: formattedResults, + affected: results.length, + }; + } catch (error) { + throw computeTwentyORMException(error); + } } override set( diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/constants/calendar-throttle-max-attempts.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/constants/calendar-throttle-max-attempts.ts index f6a3866380a..051fcdd939b 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/constants/calendar-throttle-max-attempts.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/constants/calendar-throttle-max-attempts.ts @@ -1 +1 @@ -export const CALENDAR_THROTTLE_MAX_ATTEMPTS = 4; +export const CALENDAR_THROTTLE_MAX_ATTEMPTS = 5; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts index dab7ecab0e5..0e0bb0f1a64 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts @@ -1,6 +1,10 @@ import { Injectable, Logger } from '@nestjs/common'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { + TwentyORMException, + TwentyORMExceptionCode, +} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { CALENDAR_THROTTLE_MAX_ATTEMPTS } from 'src/modules/calendar/calendar-event-import-manager/constants/calendar-throttle-max-attempts'; import { @@ -31,7 +35,7 @@ export class CalendarEventImportErrorHandlerService { ) {} public async handleDriverException( - exception: CalendarEventImportDriverException, + exception: CalendarEventImportDriverException | TwentyORMException, syncStep: CalendarEventImportSyncStep, calendarChannel: Pick< CalendarChannelWorkspaceEntity, @@ -47,6 +51,7 @@ export class CalendarEventImportErrorHandlerService { workspaceId, ); break; + case TwentyORMExceptionCode.QUERY_READ_TIMEOUT: case CalendarEventImportDriverExceptionCode.TEMPORARY_ERROR: await this.handleTemporaryException( syncStep, @@ -158,7 +163,7 @@ export class CalendarEventImportErrorHandlerService { } private async handleUnknownException( - exception: CalendarEventImportDriverException, + exception: { message: string }, calendarChannel: Pick, workspaceId: string, ): Promise { diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts index fc665cd17bb..a7098776a37 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts @@ -118,7 +118,10 @@ export class ConnectedAccountRefreshTokensService { ); } - this.logger.log(error); + this.logger.log( + `Error while refreshing tokens on connected account ${connectedAccount.id.slice(0, 7)} in workspace ${workspaceId.slice(0, 7)}`, + error, + ); throw new ConnectedAccountRefreshAccessTokenException( `Error refreshing tokens for connected account ${connectedAccount.id.slice(0, 7)} in workspace ${workspaceId.slice(0, 7)}: ${error.message} ${error?.response?.data?.error_description}`, ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_ACCESS_TOKEN_FAILED, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/constants/messaging-throttle-max-attempts.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/constants/messaging-throttle-max-attempts.ts index 319c126e2ad..c767984b3d9 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/constants/messaging-throttle-max-attempts.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/constants/messaging-throttle-max-attempts.ts @@ -1 +1 @@ -export const MESSAGING_THROTTLE_MAX_ATTEMPTS = 4; +export const MESSAGING_THROTTLE_MAX_ATTEMPTS = 5; diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-users-messages-get-batch-size.constant.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-users-messages-get-batch-size.constant.ts index dc89d1a32bc..a8a4f4abfc0 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-users-messages-get-batch-size.constant.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-users-messages-get-batch-size.constant.ts @@ -1 +1 @@ -export const MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE = 200; +export const MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE = 400; 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 172630b73d2..a8e2bd3e729 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 @@ -3,6 +3,10 @@ import { Injectable } from '@nestjs/common'; import { isDefined } from 'class-validator'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { + TwentyORMException, + TwentyORMExceptionCode, +} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { MessageChannelSyncStatusService } from 'src/modules/messaging/common/services/message-channel-sync-status.service'; import { @@ -36,7 +40,7 @@ export class MessageImportExceptionHandlerService { ) {} public async handleDriverException( - exception: MessageImportDriverException | Error, + exception: MessageImportDriverException | Error | TwentyORMException, syncStep: MessageImportSyncStep, messageChannel: Pick< MessageChannelWorkspaceEntity, @@ -53,6 +57,7 @@ export class MessageImportExceptionHandlerService { workspaceId, ); break; + case TwentyORMExceptionCode.QUERY_READ_TIMEOUT: case MessageImportDriverExceptionCode.TEMPORARY_ERROR: case MessageNetworkExceptionCode.ECONNABORTED: case MessageNetworkExceptionCode.ENOTFOUND: @@ -102,7 +107,7 @@ export class MessageImportExceptionHandlerService { 'id' | 'throttleFailureCount' >, workspaceId: string, - exception: MessageImportDriverException, + exception: { message: string }, ): Promise { if ( messageChannel.throttleFailureCount >= MESSAGING_THROTTLE_MAX_ATTEMPTS