Add TwentyORM query read timeout exception (#13603)
In this PR: - adding a try / catch around all ORM internal methods save, insert, upsert, findOne, ... - leveraging this error to prevent messageChannels to get FAILED - optimizing messaging BATCH_SIZE and THROTTLE threshold according to local tests - <img width="1510" height="851" alt="image" src="https://github.com/user-attachments/assets/802fd933-caac-4291-9cde-34a1ddf59c06" />
This commit is contained in:
+149
-143
@@ -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<string, ObjectLiteral>,
|
||||
);
|
||||
const beforeUpdateMapById = beforeUpdate.reduce(
|
||||
(acc, e: ObjectLiteral) => {
|
||||
acc[e.id] = e;
|
||||
|
||||
const objectMetadataItem = getObjectMetadataFromEntityTarget(
|
||||
entityTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, ObjectLiteral>,
|
||||
);
|
||||
|
||||
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<Entity[]>(
|
||||
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<Entity[]>(
|
||||
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<
|
||||
|
||||
+23
@@ -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;
|
||||
};
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
+54
-48
@@ -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<DeleteResult & { generatedMaps: T[] }> {
|
||||
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<T>,
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
true,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
);
|
||||
const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
|
||||
this as unknown as WorkspaceSelectQueryBuilder<T>,
|
||||
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<T[]>(
|
||||
result.raw,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
const formattedResult = formatResult<T[]>(
|
||||
result.raw,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
const formattedBefore = formatResult<T[]>(
|
||||
before,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
const formattedBefore = formatResult<T[]>(
|
||||
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<T> {
|
||||
|
||||
+90
-85
@@ -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<InsertResult> {
|
||||
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<T>,
|
||||
this.objectRecordsPermissions,
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
);
|
||||
|
||||
const updatedValues =
|
||||
await this.relationNestedQueries.processRelationNestedQueries({
|
||||
entities: this.expressionMap.valuesSet as
|
||||
| QueryDeepPartialEntityWithNestedRelationFields<T>
|
||||
| QueryDeepPartialEntityWithNestedRelationFields<T>[],
|
||||
relationNestedConfig: this.relationNestedConfig,
|
||||
queryBuilder: nestedRelationQueryBuilder,
|
||||
});
|
||||
if (isDefined(this.relationNestedConfig)) {
|
||||
const nestedRelationQueryBuilder = new WorkspaceSelectQueryBuilder(
|
||||
this as unknown as WorkspaceSelectQueryBuilder<T>,
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
);
|
||||
|
||||
this.expressionMap.valuesSet = updatedValues;
|
||||
const updatedValues =
|
||||
await this.relationNestedQueries.processRelationNestedQueries({
|
||||
entities: this.expressionMap.valuesSet as
|
||||
| QueryDeepPartialEntityWithNestedRelationFields<T>
|
||||
| QueryDeepPartialEntityWithNestedRelationFields<T>[],
|
||||
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<T>;
|
||||
|
||||
eventSelectQueryBuilder.whereInIds(
|
||||
result.identifiers.map((identifier) => identifier.id),
|
||||
);
|
||||
|
||||
const afterResult = await eventSelectQueryBuilder.getMany();
|
||||
|
||||
const formattedResultForEvent = formatResult<T[]>(
|
||||
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<string, string>) =>
|
||||
Object.keys(rawResult)
|
||||
.filter((key) => this.expressionMap.returning.includes(key))
|
||||
.reduce((filtered: Record<string, string>, key) => {
|
||||
filtered[key] = rawResult[key];
|
||||
|
||||
return filtered;
|
||||
}, {}),
|
||||
);
|
||||
|
||||
const formattedResult = formatResult<T[]>(
|
||||
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<T>;
|
||||
|
||||
eventSelectQueryBuilder.whereInIds(
|
||||
result.identifiers.map((identifier) => identifier.id),
|
||||
);
|
||||
|
||||
const afterResult = await eventSelectQueryBuilder.getMany();
|
||||
|
||||
const formattedResultForEvent = formatResult<T[]>(
|
||||
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<string, string>) =>
|
||||
Object.keys(rawResult)
|
||||
.filter((key) => this.expressionMap.returning.includes(key))
|
||||
.reduce((filtered: Record<string, string>, key) => {
|
||||
filtered[key] = rawResult[key];
|
||||
|
||||
return filtered;
|
||||
}, {}),
|
||||
);
|
||||
|
||||
const formattedResult = formatResult<T[]>(
|
||||
resultWithoutInsertionExtraColumns,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return {
|
||||
raw: resultWithoutInsertionExtraColumns,
|
||||
generatedMaps: formattedResult,
|
||||
identifiers: result.identifiers,
|
||||
};
|
||||
}
|
||||
|
||||
private getMainAliasTarget(): EntityTarget<T> {
|
||||
|
||||
+108
-75
@@ -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<any> {
|
||||
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<T[]>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
const formattedResult = formatResult<T[]>(
|
||||
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<T[]> {
|
||||
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<T[]>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
const formattedResult = formatResult<T[]>(
|
||||
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<U = any>(): Promise<U | undefined> {
|
||||
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<U = any>(): Promise<U[]> {
|
||||
this.validatePermissions();
|
||||
try {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getRawMany();
|
||||
return super.getRawMany();
|
||||
} catch (error) {
|
||||
throw computeTwentyORMException(error);
|
||||
}
|
||||
}
|
||||
|
||||
override async getOne(): Promise<T | null> {
|
||||
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<T>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
const formattedResult = formatResult<T>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return formattedResult;
|
||||
return formattedResult;
|
||||
} catch (error) {
|
||||
throw computeTwentyORMException(error);
|
||||
}
|
||||
}
|
||||
|
||||
override async getOneOrFail(): Promise<T> {
|
||||
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<T>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
const formattedResult = formatResult<T>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return formattedResult[0];
|
||||
return formattedResult[0];
|
||||
} catch (error) {
|
||||
throw computeTwentyORMException(error);
|
||||
}
|
||||
}
|
||||
|
||||
override getCount(): Promise<number> {
|
||||
this.validatePermissions();
|
||||
try {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getCount();
|
||||
return super.getCount();
|
||||
} catch (error) {
|
||||
throw computeTwentyORMException(error);
|
||||
}
|
||||
}
|
||||
|
||||
override getExists(): Promise<boolean> {
|
||||
@@ -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<T[]>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
const formattedResult = formatResult<T[]>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return [formattedResult, count];
|
||||
return [formattedResult, count];
|
||||
} catch (error) {
|
||||
throw computeTwentyORMException(error);
|
||||
}
|
||||
}
|
||||
|
||||
override insert(): WorkspaceInsertQueryBuilder<T> {
|
||||
|
||||
+36
-31
@@ -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<UpdateResult> {
|
||||
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<T[]>(
|
||||
after.raw,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
const formattedAfter = formatResult<T[]>(
|
||||
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<T> {
|
||||
|
||||
+174
-163
@@ -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<UpdateResult> {
|
||||
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<T>,
|
||||
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<T>,
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
);
|
||||
|
||||
if (isDefined(this.relationNestedConfig)) {
|
||||
const updatedValues =
|
||||
await this.relationNestedQueries.processRelationNestedQueries({
|
||||
entities: this.expressionMap.valuesSet as
|
||||
| QueryDeepPartialEntityWithNestedRelationFields<T>
|
||||
| QueryDeepPartialEntityWithNestedRelationFields<T>[],
|
||||
relationNestedConfig: this.relationNestedConfig,
|
||||
queryBuilder: nestedRelationQueryBuilder,
|
||||
});
|
||||
|
||||
this.expressionMap.valuesSet =
|
||||
updatedValues.length === 1 ? updatedValues[0] : updatedValues;
|
||||
}
|
||||
|
||||
const formattedBefore = formatResult<T[]>(
|
||||
before,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
const result = await super.execute();
|
||||
const after = await eventSelectQueryBuilder.getMany();
|
||||
|
||||
const formattedAfter = formatResult<T[]>(
|
||||
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<T[]>(
|
||||
result.raw,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return {
|
||||
raw: result.raw,
|
||||
generatedMaps: formattedResult,
|
||||
affected: result.affected,
|
||||
};
|
||||
}
|
||||
|
||||
public async executeMany(): Promise<UpdateResult> {
|
||||
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<T>,
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
true,
|
||||
this.authContext,
|
||||
this.featureFlagMap,
|
||||
);
|
||||
const eventSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
|
||||
this as unknown as WorkspaceSelectQueryBuilder<T>,
|
||||
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<T[]>(
|
||||
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<T>,
|
||||
@@ -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<T>
|
||||
| QueryDeepPartialEntityWithNestedRelationFields<T>[],
|
||||
relationNestedConfig: this.relationNestedConfig,
|
||||
@@ -253,39 +140,163 @@ export class WorkspaceUpdateQueryBuilder<
|
||||
updatedValues.length === 1 ? updatedValues[0] : updatedValues;
|
||||
}
|
||||
|
||||
const formattedBefore = formatResult<T[]>(
|
||||
before,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
const result = await super.execute();
|
||||
const after = await eventSelectQueryBuilder.getMany();
|
||||
|
||||
results.push(result);
|
||||
const formattedAfter = formatResult<T[]>(
|
||||
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<T[]>(
|
||||
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<UpdateResult> {
|
||||
try {
|
||||
for (const input of this.manyInputs) {
|
||||
const fakeExpressionMapToValidatePermissions = Object.assign(
|
||||
{},
|
||||
this.expressionMap,
|
||||
{
|
||||
wheres: input.criteria,
|
||||
valuesSet: input.partialEntity,
|
||||
},
|
||||
);
|
||||
|
||||
const formattedAfter = formatResult<T[]>(
|
||||
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<T[]>(
|
||||
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<T>,
|
||||
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<T[]>(
|
||||
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<T>,
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
);
|
||||
|
||||
if (isDefined(this.relationNestedConfig)) {
|
||||
const updatedValues =
|
||||
await this.relationNestedQueries.processRelationNestedQueries({
|
||||
entities: input.partialEntity as
|
||||
| QueryDeepPartialEntityWithNestedRelationFields<T>
|
||||
| QueryDeepPartialEntityWithNestedRelationFields<T>[],
|
||||
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<T[]>(
|
||||
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<T[]>(
|
||||
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(
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export const CALENDAR_THROTTLE_MAX_ATTEMPTS = 4;
|
||||
export const CALENDAR_THROTTLE_MAX_ATTEMPTS = 5;
|
||||
|
||||
+7
-2
@@ -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<CalendarChannelWorkspaceEntity, 'id'>,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
|
||||
+4
-1
@@ -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,
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export const MESSAGING_THROTTLE_MAX_ATTEMPTS = 4;
|
||||
export const MESSAGING_THROTTLE_MAX_ATTEMPTS = 5;
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export const MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE = 200;
|
||||
export const MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE = 400;
|
||||
|
||||
+7
-2
@@ -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<void> {
|
||||
if (
|
||||
messageChannel.throttleFailureCount >= MESSAGING_THROTTLE_MAX_ATTEMPTS
|
||||
|
||||
Reference in New Issue
Block a user