Add db event emitter in twenty orm (#13167)
## Context Add an eventEmitter instance to twenty datasources so we can emit DB events. Add input and output formatting to twenty orm (formatData, formatResult) Those 2 elements simplified existing logic when we interact with the ORM, input will be formatted by the ORM so we can directly use field-like structure instead of column-like. The output will be formatted, for builder queries it will be in `result.generatedMaps` where `result.raw` preserves the previous column-like structure. Important change: We now have an authContext that we can pass when we get a repository, this will be used for the different events emitted in the ORM. We also removed the caching for repositories as it was not scaling well and not necessary imho Note: An upcoming PR should handle the onDelete: cascade behavior where we send DESTROY events in cascade when there is an onDelete: CASCADE on the FK. --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -15,6 +15,7 @@ import { EntityManagerFactory } from 'typeorm/entity-manager/EntityManagerFactor
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@ -58,20 +59,29 @@ export class WorkspaceDataSource extends DataSource {
|
||||
target: EntityTarget<Entity>,
|
||||
shouldBypassPermissionChecks = false,
|
||||
roleId?: string,
|
||||
authContext?: AuthContext,
|
||||
): WorkspaceRepository<Entity> {
|
||||
if (shouldBypassPermissionChecks === true) {
|
||||
return this.manager.getRepository(target, {
|
||||
shouldBypassPermissionChecks: true,
|
||||
});
|
||||
return this.manager.getRepository(
|
||||
target,
|
||||
{
|
||||
shouldBypassPermissionChecks: true,
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
if (roleId) {
|
||||
return this.manager.getRepository(target, {
|
||||
roleId,
|
||||
});
|
||||
return this.manager.getRepository(
|
||||
target,
|
||||
{
|
||||
roleId,
|
||||
},
|
||||
authContext,
|
||||
);
|
||||
}
|
||||
|
||||
return this.manager.getRepository(target);
|
||||
return this.manager.getRepository(target, undefined, authContext);
|
||||
}
|
||||
|
||||
override createEntityManager(
|
||||
|
||||
@ -55,8 +55,64 @@ describe('WorkspaceEntityManager', () => {
|
||||
mockInternalContext = {
|
||||
workspaceId: 'test-workspace-id',
|
||||
objectMetadataMaps: {
|
||||
idByNameSingular: {},
|
||||
byId: {
|
||||
'test-entity-id': {
|
||||
id: 'test-entity-id',
|
||||
nameSingular: 'test-entity',
|
||||
namePlural: 'test-entities',
|
||||
labelSingular: 'Test Entity',
|
||||
labelPlural: 'Test Entities',
|
||||
workspaceId: 'test-workspace-id',
|
||||
icon: 'test-icon',
|
||||
color: 'test-color',
|
||||
isCustom: false,
|
||||
isRemote: false,
|
||||
isAuditLogged: false,
|
||||
isSearchable: false,
|
||||
isSystem: false,
|
||||
isActive: true,
|
||||
targetTableName: 'test_entity',
|
||||
indexMetadatas: [],
|
||||
fieldsById: {
|
||||
'field-id': {
|
||||
id: 'field-id',
|
||||
type: 'TEXT',
|
||||
name: 'fieldName',
|
||||
label: 'Field Name',
|
||||
objectMetadataId: 'test-entity-id',
|
||||
isNullable: true,
|
||||
isLabelSyncedWithName: false,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
},
|
||||
fieldIdByName: { fieldName: 'field-id' },
|
||||
fieldIdByJoinColumnName: {},
|
||||
},
|
||||
},
|
||||
idByNameSingular: {
|
||||
'test-entity': 'test-entity-id',
|
||||
},
|
||||
},
|
||||
featureFlagsMap: {
|
||||
IS_AIRTABLE_INTEGRATION_ENABLED: false,
|
||||
IS_POSTGRESQL_INTEGRATION_ENABLED: false,
|
||||
IS_STRIPE_INTEGRATION_ENABLED: false,
|
||||
IS_UNIQUE_INDEXES_ENABLED: false,
|
||||
IS_JSON_FILTER_ENABLED: false,
|
||||
IS_AI_ENABLED: false,
|
||||
IS_IMAP_SMTP_CALDAV_ENABLED: false,
|
||||
IS_MORPH_RELATION_ENABLED: false,
|
||||
IS_WORKFLOW_FILTERING_ENABLED: false,
|
||||
IS_RELATION_CONNECT_ENABLED: false,
|
||||
IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED: false,
|
||||
IS_FIELDS_PERMISSIONS_ENABLED: false,
|
||||
},
|
||||
eventEmitterService: {
|
||||
emitMutationEvent: jest.fn(),
|
||||
emitDatabaseBatchEvent: jest.fn(),
|
||||
emitCustomBatchEvent: jest.fn(),
|
||||
} as any,
|
||||
} as WorkspaceInternalContext;
|
||||
|
||||
mockDataSource = {
|
||||
|
||||
@ -8,6 +8,7 @@ import {
|
||||
FindManyOptions,
|
||||
FindOneOptions,
|
||||
FindOptionsWhere,
|
||||
In,
|
||||
InsertResult,
|
||||
ObjectId,
|
||||
ObjectLiteral,
|
||||
@ -32,6 +33,8 @@ import { InstanceChecker } from 'typeorm/util/InstanceChecker';
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
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 {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
@ -51,6 +54,8 @@ import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/wo
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
import { computeRelationConnectQueryConfigs } from 'src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util';
|
||||
import { createSqlWhereTupleInClause } from 'src/engine/twenty-orm/utils/create-sql-where-tuple-in-clause.utils';
|
||||
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 { getRecordToConnectFields } from 'src/engine/twenty-orm/utils/get-record-to-connect-fields.util';
|
||||
|
||||
@ -85,22 +90,10 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
shouldBypassPermissionChecks?: boolean;
|
||||
roleId?: string;
|
||||
},
|
||||
authContext?: AuthContext,
|
||||
): WorkspaceRepository<Entity> {
|
||||
const dataSource = this.connection;
|
||||
|
||||
const repositoryKey = this.getRepositoryKey({
|
||||
target,
|
||||
dataSource,
|
||||
roleId: permissionOptions?.roleId,
|
||||
shouldBypassPermissionChecks:
|
||||
permissionOptions?.shouldBypassPermissionChecks ?? false,
|
||||
});
|
||||
const repoFromMap = this.repositories.get(repositoryKey);
|
||||
|
||||
if (repoFromMap) {
|
||||
return repoFromMap as WorkspaceRepository<Entity>;
|
||||
}
|
||||
|
||||
let objectPermissions = {};
|
||||
|
||||
if (permissionOptions?.roleId) {
|
||||
@ -128,10 +121,9 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
this.queryRunner,
|
||||
objectPermissions,
|
||||
permissionOptions?.shouldBypassPermissionChecks,
|
||||
authContext,
|
||||
);
|
||||
|
||||
this.repositories.set(repositoryKey, newRepository);
|
||||
|
||||
return newRepository;
|
||||
}
|
||||
|
||||
@ -360,32 +352,6 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
.execute();
|
||||
}
|
||||
|
||||
private getRepositoryKey({
|
||||
target,
|
||||
dataSource,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
}: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
target: EntityTarget<unknown>;
|
||||
dataSource: WorkspaceDataSource;
|
||||
shouldBypassPermissionChecks: boolean;
|
||||
roleId?: string;
|
||||
}) {
|
||||
const repositoryPrefix = dataSource.getMetadata(target).name;
|
||||
const roleIdSuffix = roleId ? `_${roleId}` : '';
|
||||
const rolesPermissionsVersionSuffix = dataSource.rolesPermissionsVersion
|
||||
? `_${dataSource.rolesPermissionsVersion}`
|
||||
: '';
|
||||
const featureFlagMapVersionSuffix = dataSource.featureFlagMapVersion
|
||||
? `_${dataSource.featureFlagMapVersion}`
|
||||
: '';
|
||||
|
||||
return shouldBypassPermissionChecks
|
||||
? `${repositoryPrefix}_bypass${featureFlagMapVersionSuffix}`
|
||||
: `${repositoryPrefix}${roleIdSuffix}${rolesPermissionsVersionSuffix}${featureFlagMapVersionSuffix}`;
|
||||
}
|
||||
|
||||
validatePermissions<Entity extends ObjectLiteral>(
|
||||
target: EntityTarget<Entity> | Entity,
|
||||
operationType: OperationType,
|
||||
@ -900,6 +866,13 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
entityLike: DeepPartial<Entity>,
|
||||
permissionOptions?: PermissionOptions,
|
||||
): Promise<Entity | undefined> {
|
||||
const objectMetadataItem = getObjectMetadataFromEntityTarget(
|
||||
entityClass,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const formattedEntityLike = formatData(entityLike, objectMetadataItem);
|
||||
|
||||
const managerWithPermissionOptions = Object.assign(
|
||||
Object.create(Object.getPrototypeOf(this)),
|
||||
this,
|
||||
@ -915,12 +888,16 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
new PlainObjectToDatabaseEntityTransformer(managerWithPermissionOptions);
|
||||
const transformedEntity =
|
||||
await plainObjectToDatabaseEntityTransformer.transform(
|
||||
entityLike,
|
||||
formattedEntityLike,
|
||||
metadata,
|
||||
);
|
||||
|
||||
if (transformedEntity)
|
||||
return this.merge(entityClass, transformedEntity, entityLike) as Entity;
|
||||
return this.merge(
|
||||
entityClass,
|
||||
transformedEntity,
|
||||
formattedEntityLike,
|
||||
) as Entity;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
@ -1075,17 +1052,81 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
const queryRunnerForEntityPersistExecutor =
|
||||
this.connection.createQueryRunnerForEntityPersistExecutor();
|
||||
|
||||
return new EntityPersistExecutor(
|
||||
const isEntityArray = Array.isArray(entity);
|
||||
const entityTarget =
|
||||
target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor);
|
||||
|
||||
const entityArray = isEntityArray ? entity : [entity];
|
||||
const entityIds = entityArray.map((e) => (e as { id: string }).id);
|
||||
const beforeUpdate = await this.find(
|
||||
entityTarget,
|
||||
{
|
||||
where: { id: In(entityIds) },
|
||||
},
|
||||
permissionOptions,
|
||||
);
|
||||
|
||||
const beforeUpdateMapById = beforeUpdate.reduce(
|
||||
(acc, e: ObjectLiteral) => {
|
||||
acc[e.id] = e;
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, ObjectLiteral>,
|
||||
);
|
||||
|
||||
const objectMetadataItem = getObjectMetadataFromEntityTarget(
|
||||
entityTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const formattedEntityOrEntities = formatData(
|
||||
entityArray,
|
||||
objectMetadataItem,
|
||||
);
|
||||
|
||||
const result = await new EntityPersistExecutor(
|
||||
this.connection,
|
||||
queryRunnerForEntityPersistExecutor,
|
||||
'save',
|
||||
target,
|
||||
entity as ObjectLiteral,
|
||||
formattedEntityOrEntities as ObjectLiteral[],
|
||||
options as SaveOptions | (SaveOptions & { reload: false }),
|
||||
)
|
||||
.execute()
|
||||
.then(() => entity as Entity)
|
||||
.then(() => formattedEntityOrEntities as Entity[])
|
||||
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
||||
|
||||
const resultArray = Array.isArray(result) ? result : [result];
|
||||
|
||||
const formattedResult = formatResult<Entity[]>(
|
||||
resultArray,
|
||||
objectMetadataItem,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
for (const entity of formattedResult) {
|
||||
const isUpdate = beforeUpdateMapById[entity.id];
|
||||
|
||||
if (isUpdate) {
|
||||
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||
action: DatabaseEventAction.UPDATED,
|
||||
objectMetadataItem,
|
||||
workspaceId: this.internalContext.workspaceId,
|
||||
entities: [entity],
|
||||
beforeEntities: beforeUpdateMapById[entity.id],
|
||||
});
|
||||
} else {
|
||||
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||
action: DatabaseEventAction.CREATED,
|
||||
objectMetadataItem,
|
||||
workspaceId: this.internalContext.workspaceId,
|
||||
entities: [entity],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return isEntityArray ? formattedResult : formattedResult[0];
|
||||
}
|
||||
|
||||
override remove<Entity>(
|
||||
@ -1145,23 +1186,49 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
? maybeOptionsOrMaybePermissionOptions
|
||||
: entityOrMaybeOptions;
|
||||
|
||||
if (Array.isArray(entity) && entity.length === 0)
|
||||
return Promise.resolve(entity);
|
||||
const isEntityArray = Array.isArray(entity);
|
||||
|
||||
if (isEntityArray && entity.length === 0) return Promise.resolve(entity);
|
||||
|
||||
const queryRunnerForEntityPersistExecutor =
|
||||
this.connection.createQueryRunnerForEntityPersistExecutor();
|
||||
|
||||
return new EntityPersistExecutor(
|
||||
const entityTarget =
|
||||
target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor);
|
||||
|
||||
const objectMetadataItem = getObjectMetadataFromEntityTarget(
|
||||
entityTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const formattedEntity = formatData(entity, objectMetadataItem);
|
||||
|
||||
const result = new EntityPersistExecutor(
|
||||
this.connection,
|
||||
queryRunnerForEntityPersistExecutor,
|
||||
'remove',
|
||||
target as string | undefined,
|
||||
entity as ObjectLiteral,
|
||||
formattedEntity as ObjectLiteral,
|
||||
options as RemoveOptions,
|
||||
)
|
||||
.execute()
|
||||
.then(() => entity as Entity | Entity[])
|
||||
.then(() => formattedEntity as Entity | Entity[])
|
||||
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
||||
|
||||
const formattedResult = formatResult<Entity[]>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||
action: DatabaseEventAction.DESTROYED,
|
||||
objectMetadataItem,
|
||||
workspaceId: this.internalContext.workspaceId,
|
||||
entities: formattedResult,
|
||||
});
|
||||
|
||||
return isEntityArray ? formattedResult : formattedResult[0];
|
||||
}
|
||||
|
||||
override softRemove<Entity extends ObjectLiteral>(
|
||||
@ -1237,17 +1304,43 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
const queryRunnerForEntityPersistExecutor =
|
||||
this.connection.createQueryRunnerForEntityPersistExecutor();
|
||||
|
||||
return new EntityPersistExecutor(
|
||||
const isEntityArray = Array.isArray(entity);
|
||||
const entityTarget =
|
||||
target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor);
|
||||
|
||||
const objectMetadataItem = getObjectMetadataFromEntityTarget(
|
||||
entityTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const formattedEntity = formatData(entity, objectMetadataItem);
|
||||
|
||||
const result = new EntityPersistExecutor(
|
||||
this.connection,
|
||||
queryRunnerForEntityPersistExecutor,
|
||||
'soft-remove',
|
||||
target,
|
||||
entity as ObjectLiteral,
|
||||
formattedEntity as ObjectLiteral,
|
||||
options as SaveOptions,
|
||||
)
|
||||
.execute()
|
||||
.then(() => entity as Entity)
|
||||
.then(() => formattedEntity as Entity)
|
||||
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
||||
|
||||
const formattedResult = formatResult<Entity[]>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||
action: DatabaseEventAction.DELETED,
|
||||
objectMetadataItem,
|
||||
workspaceId: this.internalContext.workspaceId,
|
||||
entities: formattedResult,
|
||||
});
|
||||
|
||||
return isEntityArray ? formattedResult : formattedResult[0];
|
||||
}
|
||||
|
||||
override recover<Entity>(
|
||||
@ -1313,23 +1406,49 @@ export class WorkspaceEntityManager extends EntityManager {
|
||||
: entityOrEntitiesOrMaybeOptions;
|
||||
|
||||
if (InstanceChecker.isEntitySchema(target)) target = target.options.name;
|
||||
if (Array.isArray(entity) && entity.length === 0)
|
||||
return Promise.resolve(entity);
|
||||
const isEntityArray = Array.isArray(entity);
|
||||
|
||||
if (isEntityArray && entity.length === 0) return Promise.resolve(entity);
|
||||
|
||||
const queryRunnerForEntityPersistExecutor =
|
||||
this.connection.createQueryRunnerForEntityPersistExecutor();
|
||||
|
||||
return new EntityPersistExecutor(
|
||||
const entityTarget =
|
||||
target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor);
|
||||
|
||||
const objectMetadataItem = getObjectMetadataFromEntityTarget(
|
||||
entityTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const formattedEntity = formatData(entity, objectMetadataItem);
|
||||
|
||||
const result = new EntityPersistExecutor(
|
||||
this.connection,
|
||||
queryRunnerForEntityPersistExecutor,
|
||||
'recover',
|
||||
target,
|
||||
entity as ObjectLiteral,
|
||||
formattedEntity as ObjectLiteral,
|
||||
options as SaveOptions,
|
||||
)
|
||||
.execute()
|
||||
.then(() => entity as Entity)
|
||||
.then(() => formattedEntity as Entity)
|
||||
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
||||
|
||||
const formattedResult = formatResult<Entity[]>(
|
||||
result,
|
||||
objectMetadataItem,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||
action: DatabaseEventAction.RESTORED,
|
||||
objectMetadataItem,
|
||||
workspaceId: this.internalContext.workspaceId,
|
||||
entities: formattedResult,
|
||||
});
|
||||
|
||||
return isEntityArray ? formattedResult : formattedResult[0];
|
||||
}
|
||||
|
||||
// Forbidden methods
|
||||
|
||||
@ -21,4 +21,6 @@ export enum TwentyORMExceptionCode {
|
||||
CONNECT_RECORD_NOT_FOUND = 'CONNECT_RECORD_NOT_FOUND',
|
||||
CONNECT_NOT_ALLOWED = 'CONNECT_NOT_ALLOWED',
|
||||
CONNECT_UNIQUE_CONSTRAINT_ERROR = 'CONNECT_UNIQUE_CONSTRAINT_ERROR',
|
||||
MISSING_MAIN_ALIAS_TARGET = 'MISSING_MAIN_ALIAS_TARGET',
|
||||
METHOD_NOT_ALLOWED = 'METHOD_NOT_ALLOWED',
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ import { PromiseMemoizer } from 'src/engine/twenty-orm/storage/promise-memoizer.
|
||||
import { CacheKey } from 'src/engine/twenty-orm/storage/types/cache-key.type';
|
||||
import { getFromCacheWithRecompute } from 'src/engine/utils/get-data-from-cache-with-recompute.util';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
|
||||
type CacheResult<T, U> = {
|
||||
version: T;
|
||||
@ -51,6 +52,7 @@ export class WorkspaceDatasourceFactory {
|
||||
private readonly workspaceFeatureFlagsMapCacheService: WorkspaceFeatureFlagsMapCacheService,
|
||||
@InjectRepository(Workspace, 'core')
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||
) {}
|
||||
|
||||
private async conditionalDestroyDataSource(
|
||||
@ -192,6 +194,7 @@ export class WorkspaceDatasourceFactory {
|
||||
workspaceId,
|
||||
objectMetadataMaps: cachedObjectMetadataMaps,
|
||||
featureFlagsMap: cachedFeatureFlagMap,
|
||||
eventEmitterService: this.workspaceEventEmitter,
|
||||
},
|
||||
{
|
||||
url:
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||
|
||||
export interface WorkspaceInternalContext {
|
||||
workspaceId: string;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
featureFlagsMap: Record<FeatureFlagKey, boolean>;
|
||||
eventEmitterService: WorkspaceEventEmitter;
|
||||
}
|
||||
|
||||
@ -97,24 +97,20 @@ describe('WorkspaceRepository', () => {
|
||||
id: 'test-metadata-id',
|
||||
nameSingular: 'test-entity',
|
||||
namePlural: 'test-entities',
|
||||
fields: [],
|
||||
fieldIdByName: {
|
||||
id: 'test-field-id',
|
||||
},
|
||||
fieldIdByJoinColumnName: {},
|
||||
fieldsById: {
|
||||
'test-field-id': {
|
||||
id: 'test-field-id',
|
||||
name: 'id',
|
||||
type: 'string',
|
||||
isNullable: false,
|
||||
isUnique: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
jest.spyOn(repository as any, 'formatData').mockImplementation((data) => {
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((item) => Object.assign({}, item));
|
||||
}
|
||||
|
||||
return Object.assign({}, data);
|
||||
});
|
||||
|
||||
jest.spyOn(repository as any, 'formatResult').mockImplementation((data) => {
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((item) => Object.assign({}, item));
|
||||
}
|
||||
|
||||
return Object.assign({}, data);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Find Methods', () => {
|
||||
@ -239,7 +235,7 @@ describe('WorkspaceRepository', () => {
|
||||
|
||||
it('should delegate to workspaceEntityManager delete', async () => {
|
||||
const criteria: FindOptionsWhere<ObjectLiteral> = { id: 'test-id' };
|
||||
const expectedResult = { affected: 1, raw: [] };
|
||||
const expectedResult = { affected: 1, raw: [], generatedMaps: [] };
|
||||
|
||||
mockEntityManager.delete.mockResolvedValue(expectedResult);
|
||||
|
||||
@ -2,6 +2,7 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import {
|
||||
DeleteQueryBuilder,
|
||||
DeleteResult,
|
||||
EntityTarget,
|
||||
InsertQueryBuilder,
|
||||
ObjectLiteral,
|
||||
} from 'typeorm';
|
||||
@ -9,10 +10,18 @@ import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
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 {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
||||
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
||||
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';
|
||||
|
||||
export class WorkspaceDeleteQueryBuilder<
|
||||
T extends ObjectLiteral,
|
||||
@ -20,16 +29,19 @@ export class WorkspaceDeleteQueryBuilder<
|
||||
private objectRecordsPermissions: ObjectRecordsPermissions;
|
||||
private shouldBypassPermissionChecks: boolean;
|
||||
private internalContext: WorkspaceInternalContext;
|
||||
private authContext?: AuthContext;
|
||||
constructor(
|
||||
queryBuilder: DeleteQueryBuilder<T>,
|
||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||
internalContext: WorkspaceInternalContext,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
authContext?: AuthContext,
|
||||
) {
|
||||
super(queryBuilder);
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.internalContext = internalContext;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.authContext = authContext;
|
||||
}
|
||||
|
||||
override clone(): this {
|
||||
@ -40,10 +52,11 @@ export class WorkspaceDeleteQueryBuilder<
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
) as this;
|
||||
}
|
||||
|
||||
override execute(): Promise<DeleteResult> {
|
||||
override async execute(): Promise<DeleteResult> {
|
||||
validateQueryIsPermittedOrThrow(
|
||||
this.expressionMap,
|
||||
this.objectRecordsPermissions,
|
||||
@ -51,11 +64,54 @@ export class WorkspaceDeleteQueryBuilder<
|
||||
this.shouldBypassPermissionChecks,
|
||||
);
|
||||
|
||||
return super.execute();
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const result = await super.execute();
|
||||
|
||||
const formattedResult = formatResult<T[]>(
|
||||
result.raw,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||
action: DatabaseEventAction.DESTROYED,
|
||||
objectMetadataItem: objectMetadata,
|
||||
workspaceId: this.internalContext.workspaceId,
|
||||
entities: formattedResult,
|
||||
authContext: this.authContext,
|
||||
});
|
||||
|
||||
return {
|
||||
raw: result.raw,
|
||||
generatedMaps: formattedResult,
|
||||
affected: result.affected,
|
||||
};
|
||||
}
|
||||
|
||||
private getMainAliasTarget(): EntityTarget<T> {
|
||||
const mainAliasTarget = this.expressionMap.mainAlias?.target;
|
||||
|
||||
if (!mainAliasTarget) {
|
||||
throw new TwentyORMException(
|
||||
'Main alias target is missing',
|
||||
TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET,
|
||||
);
|
||||
}
|
||||
|
||||
return mainAliasTarget;
|
||||
}
|
||||
|
||||
override select(): WorkspaceSelectQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a select builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a select builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override update(): WorkspaceUpdateQueryBuilder<T>;
|
||||
@ -67,18 +123,30 @@ export class WorkspaceDeleteQueryBuilder<
|
||||
override update(
|
||||
_updateSet?: QueryDeepPartialEntity<T>,
|
||||
): WorkspaceUpdateQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into an update builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into an update builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override insert(): InsertQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into an insert builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into an insert builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a soft delete builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a soft delete builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a soft delete builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a soft delete builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,28 @@
|
||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import { InsertQueryBuilder, ObjectLiteral } from 'typeorm';
|
||||
import {
|
||||
EntityTarget,
|
||||
InsertQueryBuilder,
|
||||
InsertResult,
|
||||
ObjectLiteral,
|
||||
} from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
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 {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
||||
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
||||
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';
|
||||
|
||||
export class WorkspaceInsertQueryBuilder<
|
||||
T extends ObjectLiteral,
|
||||
@ -15,17 +30,20 @@ export class WorkspaceInsertQueryBuilder<
|
||||
private objectRecordsPermissions: ObjectRecordsPermissions;
|
||||
private shouldBypassPermissionChecks: boolean;
|
||||
private internalContext: WorkspaceInternalContext;
|
||||
private authContext?: AuthContext;
|
||||
|
||||
constructor(
|
||||
queryBuilder: InsertQueryBuilder<T>,
|
||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||
internalContext: WorkspaceInternalContext,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
authContext?: AuthContext,
|
||||
) {
|
||||
super(queryBuilder);
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.internalContext = internalContext;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.authContext = authContext;
|
||||
}
|
||||
|
||||
override clone(): this {
|
||||
@ -36,11 +54,26 @@ export class WorkspaceInsertQueryBuilder<
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
) as this;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
override execute(): Promise<any> {
|
||||
override values(
|
||||
values: QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[],
|
||||
): this {
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const formattedValues = formatData(values, objectMetadata);
|
||||
|
||||
return super.values(formattedValues);
|
||||
}
|
||||
|
||||
override async execute(): Promise<InsertResult> {
|
||||
validateQueryIsPermittedOrThrow(
|
||||
this.expressionMap,
|
||||
this.objectRecordsPermissions,
|
||||
@ -48,26 +81,81 @@ export class WorkspaceInsertQueryBuilder<
|
||||
this.shouldBypassPermissionChecks,
|
||||
);
|
||||
|
||||
return super.execute();
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const result = await super.execute();
|
||||
|
||||
const formattedResult = formatResult<T[]>(
|
||||
result.raw,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||
action: DatabaseEventAction.CREATED,
|
||||
objectMetadataItem: objectMetadata,
|
||||
workspaceId: this.internalContext.workspaceId,
|
||||
entities: formattedResult,
|
||||
authContext: this.authContext,
|
||||
});
|
||||
|
||||
return {
|
||||
raw: result.raw,
|
||||
generatedMaps: formattedResult,
|
||||
identifiers: result.identifiers,
|
||||
};
|
||||
}
|
||||
|
||||
private getMainAliasTarget(): EntityTarget<T> {
|
||||
const mainAliasTarget = this.expressionMap.mainAlias?.target;
|
||||
|
||||
if (!mainAliasTarget) {
|
||||
throw new TwentyORMException(
|
||||
'Main alias target is missing',
|
||||
TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET,
|
||||
);
|
||||
}
|
||||
|
||||
return mainAliasTarget;
|
||||
}
|
||||
|
||||
override select(): WorkspaceSelectQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a select builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a select builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override update(): WorkspaceUpdateQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into an update builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into an update builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override delete(): WorkspaceDeleteQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a delete builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a delete builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a soft delete builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a soft delete builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a soft delete builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a soft delete builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,25 @@
|
||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
||||
import { EntityTarget, ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
||||
import { WorkspaceInsertQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-insert-query-builder';
|
||||
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
||||
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
||||
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';
|
||||
|
||||
export class WorkspaceSelectQueryBuilder<
|
||||
T extends ObjectLiteral,
|
||||
@ -20,16 +27,19 @@ export class WorkspaceSelectQueryBuilder<
|
||||
objectRecordsPermissions: ObjectRecordsPermissions;
|
||||
shouldBypassPermissionChecks: boolean;
|
||||
internalContext: WorkspaceInternalContext;
|
||||
authContext?: AuthContext;
|
||||
constructor(
|
||||
queryBuilder: SelectQueryBuilder<T>,
|
||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||
internalContext: WorkspaceInternalContext,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
authContext?: AuthContext,
|
||||
) {
|
||||
super(queryBuilder);
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.internalContext = internalContext;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.authContext = authContext;
|
||||
}
|
||||
|
||||
getFindOptions() {
|
||||
@ -44,19 +54,55 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
) as this;
|
||||
}
|
||||
|
||||
override execute(): Promise<T[]> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
override async execute(): Promise<any> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.execute();
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const result = await super.execute();
|
||||
|
||||
const formattedResult = formatResult<T[]>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return {
|
||||
raw: result,
|
||||
generatedMaps: formattedResult,
|
||||
identifiers: result.identifiers,
|
||||
};
|
||||
}
|
||||
|
||||
override getMany(): Promise<T[]> {
|
||||
override async getMany(): Promise<T[]> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getMany();
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const result = await super.getMany();
|
||||
|
||||
const formattedResult = formatResult<T[]>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return formattedResult;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
@ -73,16 +119,46 @@ export class WorkspaceSelectQueryBuilder<
|
||||
return super.getRawMany();
|
||||
}
|
||||
|
||||
override getOne(): Promise<T | null> {
|
||||
override async getOne(): Promise<T | null> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getOne();
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const result = await super.getOne();
|
||||
|
||||
const formattedResult = formatResult<T>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return formattedResult;
|
||||
}
|
||||
|
||||
override getOneOrFail(): Promise<T> {
|
||||
override async getOneOrFail(): Promise<T> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getOneOrFail();
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const result = await super.getOneOrFail();
|
||||
|
||||
const formattedResult = formatResult<T>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return formattedResult[0];
|
||||
}
|
||||
|
||||
override getCount(): Promise<number> {
|
||||
@ -98,10 +174,25 @@ export class WorkspaceSelectQueryBuilder<
|
||||
);
|
||||
}
|
||||
|
||||
override getManyAndCount(): Promise<[T[], number]> {
|
||||
override async getManyAndCount(): Promise<[T[], number]> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getManyAndCount();
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const [result, count] = await super.getManyAndCount();
|
||||
|
||||
const formattedResult = formatResult<T[]>(
|
||||
result,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
return [formattedResult, count];
|
||||
}
|
||||
|
||||
override insert(): WorkspaceInsertQueryBuilder<T> {
|
||||
@ -112,6 +203,7 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@ -133,6 +225,7 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@ -144,6 +237,7 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@ -155,6 +249,7 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@ -166,6 +261,7 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@ -184,4 +280,17 @@ export class WorkspaceSelectQueryBuilder<
|
||||
this.shouldBypassPermissionChecks,
|
||||
);
|
||||
}
|
||||
|
||||
private getMainAliasTarget(): EntityTarget<T> {
|
||||
const mainAliasTarget = this.expressionMap.mainAlias?.target;
|
||||
|
||||
if (!mainAliasTarget) {
|
||||
throw new TwentyORMException(
|
||||
'Main alias target is missing',
|
||||
TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET,
|
||||
);
|
||||
}
|
||||
|
||||
return mainAliasTarget;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,13 +1,26 @@
|
||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import { InsertQueryBuilder, ObjectLiteral, UpdateResult } from 'typeorm';
|
||||
import {
|
||||
EntityTarget,
|
||||
InsertQueryBuilder,
|
||||
ObjectLiteral,
|
||||
UpdateResult,
|
||||
} from 'typeorm';
|
||||
import { SoftDeleteQueryBuilder } from 'typeorm/query-builder/SoftDeleteQueryBuilder';
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
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 {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
||||
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';
|
||||
|
||||
export class WorkspaceSoftDeleteQueryBuilder<
|
||||
T extends ObjectLiteral,
|
||||
@ -15,17 +28,20 @@ export class WorkspaceSoftDeleteQueryBuilder<
|
||||
private objectRecordsPermissions: ObjectRecordsPermissions;
|
||||
private shouldBypassPermissionChecks: boolean;
|
||||
private internalContext: WorkspaceInternalContext;
|
||||
private authContext?: AuthContext;
|
||||
|
||||
constructor(
|
||||
queryBuilder: SoftDeleteQueryBuilder<T>,
|
||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||
internalContext: WorkspaceInternalContext,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
authContext?: AuthContext,
|
||||
) {
|
||||
super(queryBuilder);
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.internalContext = internalContext;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.authContext = authContext;
|
||||
}
|
||||
|
||||
override clone(): this {
|
||||
@ -36,10 +52,11 @@ export class WorkspaceSoftDeleteQueryBuilder<
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
) as this;
|
||||
}
|
||||
|
||||
override execute(): Promise<UpdateResult> {
|
||||
override async execute(): Promise<UpdateResult> {
|
||||
validateQueryIsPermittedOrThrow(
|
||||
this.expressionMap,
|
||||
this.objectRecordsPermissions,
|
||||
@ -47,22 +64,74 @@ export class WorkspaceSoftDeleteQueryBuilder<
|
||||
this.shouldBypassPermissionChecks,
|
||||
);
|
||||
|
||||
return super.execute();
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const after = await super.execute();
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
return {
|
||||
raw: after.raw,
|
||||
generatedMaps: formattedAfter,
|
||||
affected: after.affected,
|
||||
};
|
||||
}
|
||||
|
||||
override select(): WorkspaceSelectQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a select builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a select builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override update(): WorkspaceUpdateQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into an update builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into an update builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override insert(): InsertQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into an insert builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into an insert builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override delete(): WorkspaceDeleteQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a delete builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a delete builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
private getMainAliasTarget(): EntityTarget<T> {
|
||||
const mainAliasTarget = this.expressionMap.mainAlias?.target;
|
||||
|
||||
if (!mainAliasTarget) {
|
||||
throw new TwentyORMException(
|
||||
'Main alias target is missing',
|
||||
TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET,
|
||||
);
|
||||
}
|
||||
|
||||
return mainAliasTarget;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,12 +1,27 @@
|
||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import { ObjectLiteral, UpdateQueryBuilder, UpdateResult } from 'typeorm';
|
||||
import {
|
||||
EntityTarget,
|
||||
ObjectLiteral,
|
||||
UpdateQueryBuilder,
|
||||
UpdateResult,
|
||||
} from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
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 {
|
||||
TwentyORMException,
|
||||
TwentyORMExceptionCode,
|
||||
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
||||
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';
|
||||
|
||||
export class WorkspaceUpdateQueryBuilder<
|
||||
T extends ObjectLiteral,
|
||||
@ -14,16 +29,19 @@ export class WorkspaceUpdateQueryBuilder<
|
||||
private objectRecordsPermissions: ObjectRecordsPermissions;
|
||||
private shouldBypassPermissionChecks: boolean;
|
||||
private internalContext: WorkspaceInternalContext;
|
||||
private authContext?: AuthContext;
|
||||
constructor(
|
||||
queryBuilder: UpdateQueryBuilder<T>,
|
||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||
internalContext: WorkspaceInternalContext,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
authContext?: AuthContext,
|
||||
) {
|
||||
super(queryBuilder);
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.internalContext = internalContext;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.authContext = authContext;
|
||||
}
|
||||
|
||||
override clone(): this {
|
||||
@ -34,10 +52,11 @@ export class WorkspaceUpdateQueryBuilder<
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
) as this;
|
||||
}
|
||||
|
||||
override execute(): Promise<UpdateResult> {
|
||||
override async execute(): Promise<UpdateResult> {
|
||||
validateQueryIsPermittedOrThrow(
|
||||
this.expressionMap,
|
||||
this.objectRecordsPermissions,
|
||||
@ -45,22 +64,108 @@ export class WorkspaceUpdateQueryBuilder<
|
||||
this.shouldBypassPermissionChecks,
|
||||
);
|
||||
|
||||
return super.execute();
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const beforeSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
|
||||
this as unknown as WorkspaceSelectQueryBuilder<T>,
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
);
|
||||
|
||||
beforeSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres;
|
||||
beforeSelectQueryBuilder.expressionMap.aliases = this.expressionMap.aliases;
|
||||
beforeSelectQueryBuilder.setParameters(this.getParameters());
|
||||
|
||||
const before = await beforeSelectQueryBuilder.getMany();
|
||||
|
||||
const formattedBefore = formatResult<T[]>(
|
||||
before,
|
||||
objectMetadata,
|
||||
this.internalContext.objectMetadataMaps,
|
||||
);
|
||||
|
||||
const after = await super.execute();
|
||||
|
||||
const formattedAfter = formatResult<T[]>(
|
||||
after.raw,
|
||||
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,
|
||||
});
|
||||
|
||||
return {
|
||||
raw: after.raw,
|
||||
generatedMaps: formattedAfter,
|
||||
affected: after.affected,
|
||||
};
|
||||
}
|
||||
|
||||
override set(_values: QueryDeepPartialEntity<T>): this {
|
||||
const mainAliasTarget = this.getMainAliasTarget();
|
||||
|
||||
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||
mainAliasTarget,
|
||||
this.internalContext,
|
||||
);
|
||||
|
||||
const formattedUpdateSet = formatData(_values, objectMetadata);
|
||||
|
||||
return super.set(formattedUpdateSet);
|
||||
}
|
||||
|
||||
override select(): WorkspaceSelectQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a select builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a select builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override delete(): WorkspaceDeleteQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a delete builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a delete builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a soft delete builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a soft delete builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||
throw new Error('This builder cannot morph into a soft delete builder');
|
||||
throw new TwentyORMException(
|
||||
'This builder cannot morph into a soft delete builder',
|
||||
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||
);
|
||||
}
|
||||
|
||||
private getMainAliasTarget(): EntityTarget<T> {
|
||||
const mainAliasTarget = this.expressionMap.mainAlias?.target;
|
||||
|
||||
if (!mainAliasTarget) {
|
||||
throw new TwentyORMException(
|
||||
'Main alias target is missing',
|
||||
TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET,
|
||||
);
|
||||
}
|
||||
|
||||
return mainAliasTarget;
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,16 +22,15 @@ import { UpsertOptions } from 'typeorm/repository/UpsertOptions';
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
import {
|
||||
PermissionsException,
|
||||
PermissionsExceptionCode,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { QueryDeepPartialEntityWithRelationConnect } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-relation-connect.type';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
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';
|
||||
|
||||
export class WorkspaceRepository<
|
||||
@ -41,6 +40,7 @@ export class WorkspaceRepository<
|
||||
private shouldBypassPermissionChecks: boolean;
|
||||
private featureFlagMap: FeatureFlagMap;
|
||||
private objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||
private authContext?: AuthContext;
|
||||
declare manager: WorkspaceEntityManager;
|
||||
|
||||
constructor(
|
||||
@ -51,6 +51,7 @@ export class WorkspaceRepository<
|
||||
queryRunner?: QueryRunner,
|
||||
objectRecordsPermissions?: ObjectRecordsPermissions,
|
||||
shouldBypassPermissionChecks = false,
|
||||
authContext?: AuthContext,
|
||||
) {
|
||||
super(target, manager, queryRunner);
|
||||
this.internalContext = internalContext;
|
||||
@ -58,6 +59,7 @@ export class WorkspaceRepository<
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.manager = manager;
|
||||
this.authContext = authContext;
|
||||
}
|
||||
|
||||
override createQueryBuilder<U extends T>(
|
||||
@ -78,6 +80,7 @@ export class WorkspaceRepository<
|
||||
this.objectRecordsPermissions,
|
||||
this.internalContext,
|
||||
this.shouldBypassPermissionChecks,
|
||||
this.authContext,
|
||||
);
|
||||
}
|
||||
|
||||
@ -99,9 +102,8 @@ export class WorkspaceRepository<
|
||||
computedOptions,
|
||||
permissionOptions,
|
||||
);
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
override async findBy(
|
||||
@ -119,9 +121,8 @@ export class WorkspaceRepository<
|
||||
computedOptions.where,
|
||||
permissionOptions,
|
||||
);
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
override async findAndCount(
|
||||
@ -139,9 +140,8 @@ export class WorkspaceRepository<
|
||||
computedOptions,
|
||||
permissionOptions,
|
||||
);
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
override async findAndCountBy(
|
||||
@ -159,9 +159,8 @@ export class WorkspaceRepository<
|
||||
computedOptions.where,
|
||||
permissionOptions,
|
||||
);
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
override async findOne(
|
||||
@ -179,9 +178,8 @@ export class WorkspaceRepository<
|
||||
computedOptions,
|
||||
permissionOptions,
|
||||
);
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
override async findOneBy(
|
||||
@ -199,9 +197,8 @@ export class WorkspaceRepository<
|
||||
computedOptions.where,
|
||||
permissionOptions,
|
||||
);
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
override async findOneOrFail(
|
||||
@ -219,9 +216,8 @@ export class WorkspaceRepository<
|
||||
computedOptions,
|
||||
permissionOptions,
|
||||
);
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
override async findOneByOrFail(
|
||||
@ -239,9 +235,8 @@ export class WorkspaceRepository<
|
||||
computedOptions.where,
|
||||
permissionOptions,
|
||||
);
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -277,7 +272,6 @@ export class WorkspaceRepository<
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U | U[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||
let result: U | U[];
|
||||
|
||||
const permissionOptions = {
|
||||
@ -286,25 +280,23 @@ export class WorkspaceRepository<
|
||||
};
|
||||
|
||||
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
||||
if (Array.isArray(formattedEntityOrEntities)) {
|
||||
if (Array.isArray(entityOrEntities)) {
|
||||
result = await manager.save(
|
||||
this.target,
|
||||
formattedEntityOrEntities,
|
||||
entityOrEntities,
|
||||
options,
|
||||
permissionOptions,
|
||||
);
|
||||
} else {
|
||||
result = await manager.save(
|
||||
this.target,
|
||||
formattedEntityOrEntities,
|
||||
entityOrEntities,
|
||||
options,
|
||||
permissionOptions,
|
||||
);
|
||||
}
|
||||
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -328,21 +320,18 @@ export class WorkspaceRepository<
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T | T[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||
const permissionOptions = {
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
};
|
||||
const result = await manager.remove(
|
||||
this.target,
|
||||
formattedEntityOrEntities,
|
||||
entityOrEntities,
|
||||
options,
|
||||
permissionOptions,
|
||||
);
|
||||
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
override async delete(
|
||||
@ -402,7 +391,6 @@ export class WorkspaceRepository<
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U | U[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||
const permissionOptions = {
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
@ -410,25 +398,23 @@ export class WorkspaceRepository<
|
||||
let result: U | U[];
|
||||
|
||||
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
||||
if (Array.isArray(formattedEntityOrEntities)) {
|
||||
if (Array.isArray(entityOrEntities)) {
|
||||
result = await manager.softRemove(
|
||||
this.target,
|
||||
formattedEntityOrEntities,
|
||||
entityOrEntities,
|
||||
options,
|
||||
permissionOptions,
|
||||
);
|
||||
} else {
|
||||
result = await manager.softRemove(
|
||||
this.target,
|
||||
formattedEntityOrEntities,
|
||||
entityOrEntities,
|
||||
options,
|
||||
permissionOptions,
|
||||
);
|
||||
}
|
||||
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
override async softDelete(
|
||||
@ -491,7 +477,6 @@ export class WorkspaceRepository<
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U | U[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||
const permissionOptions = {
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
@ -499,25 +484,23 @@ export class WorkspaceRepository<
|
||||
let result: U | U[];
|
||||
|
||||
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
||||
if (Array.isArray(formattedEntityOrEntities)) {
|
||||
if (Array.isArray(entityOrEntities)) {
|
||||
result = await manager.recover(
|
||||
this.target,
|
||||
formattedEntityOrEntities,
|
||||
entityOrEntities,
|
||||
options,
|
||||
permissionOptions,
|
||||
);
|
||||
} else {
|
||||
result = await manager.recover(
|
||||
this.target,
|
||||
formattedEntityOrEntities,
|
||||
entityOrEntities,
|
||||
options,
|
||||
permissionOptions,
|
||||
);
|
||||
}
|
||||
|
||||
const formattedResult = await this.formatResult(result);
|
||||
|
||||
return formattedResult;
|
||||
return result;
|
||||
}
|
||||
|
||||
override async restore(
|
||||
@ -558,23 +541,12 @@ export class WorkspaceRepository<
|
||||
): Promise<InsertResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
const formattedEntity = await this.formatData(entity);
|
||||
const permissionOptions = {
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
};
|
||||
const result = await manager.insert(
|
||||
this.target,
|
||||
formattedEntity,
|
||||
permissionOptions,
|
||||
);
|
||||
const formattedResult = await this.formatResult(result.generatedMaps);
|
||||
|
||||
return {
|
||||
raw: result.raw,
|
||||
generatedMaps: formattedResult,
|
||||
identifiers: result.identifiers,
|
||||
};
|
||||
return manager.insert(this.target, entity, permissionOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -620,8 +592,6 @@ export class WorkspaceRepository<
|
||||
): Promise<InsertResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||
|
||||
const permissionOptions = {
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
@ -629,16 +599,14 @@ export class WorkspaceRepository<
|
||||
|
||||
const result = await manager.upsert(
|
||||
this.target,
|
||||
formattedEntityOrEntities,
|
||||
entityOrEntities,
|
||||
conflictPathsOrOptions,
|
||||
permissionOptions,
|
||||
);
|
||||
|
||||
const formattedResult = await this.formatResult(result.generatedMaps);
|
||||
|
||||
return {
|
||||
raw: result.raw,
|
||||
generatedMaps: formattedResult,
|
||||
generatedMaps: result.generatedMaps,
|
||||
identifiers: result.identifiers,
|
||||
};
|
||||
}
|
||||
@ -862,13 +830,12 @@ export class WorkspaceRepository<
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T | undefined> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formattedEntityLike = await this.formatData(entityLike);
|
||||
const permissionOptions = {
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
};
|
||||
|
||||
return manager.preload(this.target, formattedEntityLike, permissionOptions);
|
||||
return manager.preload(this.target, entityLike, permissionOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -940,15 +907,4 @@ export class WorkspaceRepository<
|
||||
|
||||
return formatData(data, objectMetadata) as T;
|
||||
}
|
||||
|
||||
async formatResult<T>(
|
||||
data: T,
|
||||
objectMetadata?: ObjectMetadataItemWithFieldMaps,
|
||||
): Promise<T> {
|
||||
objectMetadata ??= await this.getObjectMetadataFromTarget();
|
||||
|
||||
const objectMetadataMaps = this.internalContext.objectMetadataMaps;
|
||||
|
||||
return formatResult(data, objectMetadata, objectMetadataMaps) as T;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { EntitySchema, EntityTarget, ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||
import {
|
||||
TwentyORMException,
|
||||
@ -12,7 +13,7 @@ import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspac
|
||||
export const getObjectMetadataFromEntityTarget = <T extends ObjectLiteral>(
|
||||
entityTarget: EntityTarget<T>,
|
||||
internalContext: WorkspaceInternalContext,
|
||||
) => {
|
||||
): ObjectMetadataItemWithFieldMaps => {
|
||||
const objectMetadataName =
|
||||
typeof entityTarget === 'string'
|
||||
? entityTarget
|
||||
|
||||
Reference in New Issue
Block a user