[permissions] Add permissions check layer in entityManager (#11818)
First and main step of https://github.com/twentyhq/core-team-issues/issues/747 We are implementing a permission check layer in our custom WorkspaceEntityManager by overriding all the db-executing methods (this PR only overrides some as a POC, the rest will be done in the next PR). Our custom repositories call entity managers under the hood to interact with the db so this solves the repositories case too. This is still behind the feature flag IsPermissionsV2Enabled. In the next PR - finish overriding all the methods required in WorkspaceEntityManager - add tests
This commit is contained in:
@ -5,12 +5,14 @@ import {
|
||||
EntityTarget,
|
||||
ObjectLiteral,
|
||||
QueryRunner,
|
||||
ReplicationMode,
|
||||
} from 'typeorm';
|
||||
|
||||
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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/entity.manager';
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { WorkspaceQueryRunner } from 'src/engine/twenty-orm/query-runner/workspace-query-runner';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
|
||||
export class WorkspaceDataSource extends DataSource {
|
||||
@ -31,10 +33,10 @@ export class WorkspaceDataSource extends DataSource {
|
||||
) {
|
||||
super(options);
|
||||
this.internalContext = internalContext;
|
||||
// Recreate manager after internalContext has been initialized
|
||||
this.manager = this.createEntityManager();
|
||||
this.featureFlagMap = featureFlagMap;
|
||||
this.featureFlagMapVersion = featureFlagMapVersion;
|
||||
// Recreate manager after internalContext has been initialized
|
||||
this.manager = this.createEntityManager();
|
||||
this.rolesPermissionsVersion = rolesPermissionsVersion;
|
||||
this.permissionsPerRoleId = permissionsPerRoleId;
|
||||
}
|
||||
@ -65,6 +67,17 @@ export class WorkspaceDataSource extends DataSource {
|
||||
return new WorkspaceEntityManager(this.internalContext, this, queryRunner);
|
||||
}
|
||||
|
||||
override createQueryRunner(
|
||||
mode = 'master' as ReplicationMode,
|
||||
): WorkspaceQueryRunner {
|
||||
const queryRunner = this.driver.createQueryRunner(mode);
|
||||
const manager = this.createEntityManager(queryRunner);
|
||||
|
||||
Object.assign(queryRunner, { manager: manager });
|
||||
|
||||
return queryRunner as any as WorkspaceQueryRunner;
|
||||
}
|
||||
|
||||
setRolesPermissionsVersion(rolesPermissionsVersion: string) {
|
||||
this.rolesPermissionsVersion = rolesPermissionsVersion;
|
||||
}
|
||||
|
||||
@ -1,95 +0,0 @@
|
||||
import {
|
||||
DataSource,
|
||||
EntityManager,
|
||||
EntityTarget,
|
||||
ObjectLiteral,
|
||||
QueryRunner,
|
||||
Repository,
|
||||
} from 'typeorm';
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
|
||||
export class WorkspaceEntityManager extends EntityManager {
|
||||
private readonly internalContext: WorkspaceInternalContext;
|
||||
readonly repositories: Map<string, Repository<any>>;
|
||||
|
||||
constructor(
|
||||
internalContext: WorkspaceInternalContext,
|
||||
connection: DataSource,
|
||||
queryRunner?: QueryRunner,
|
||||
) {
|
||||
super(connection, queryRunner);
|
||||
this.internalContext = internalContext;
|
||||
this.repositories = new Map();
|
||||
}
|
||||
|
||||
override getRepository<Entity extends ObjectLiteral>(
|
||||
target: EntityTarget<Entity>,
|
||||
shouldBypassPermissionChecks = false,
|
||||
roleId?: string,
|
||||
): WorkspaceRepository<Entity> {
|
||||
const dataSource = this.connection as WorkspaceDataSource;
|
||||
|
||||
const repositoryKey = this.getRepositoryKey({
|
||||
target,
|
||||
dataSource,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
});
|
||||
const repoFromMap = this.repositories.get(repositoryKey);
|
||||
|
||||
if (repoFromMap) {
|
||||
return repoFromMap as WorkspaceRepository<Entity>;
|
||||
}
|
||||
|
||||
let objectPermissions = {};
|
||||
|
||||
if (roleId) {
|
||||
const objectPermissionsByRoleId = dataSource.permissionsPerRoleId;
|
||||
|
||||
objectPermissions = objectPermissionsByRoleId?.[roleId] ?? {};
|
||||
}
|
||||
|
||||
const newRepository = new WorkspaceRepository<Entity>(
|
||||
this.internalContext,
|
||||
target,
|
||||
this,
|
||||
dataSource.featureFlagMap,
|
||||
this.queryRunner,
|
||||
objectPermissions,
|
||||
shouldBypassPermissionChecks,
|
||||
);
|
||||
|
||||
this.repositories.set(repositoryKey, newRepository);
|
||||
|
||||
return newRepository;
|
||||
}
|
||||
|
||||
private getRepositoryKey({
|
||||
target,
|
||||
dataSource,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
}: {
|
||||
target: EntityTarget<any>;
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,228 @@
|
||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import {
|
||||
EntityManager,
|
||||
EntityTarget,
|
||||
InsertResult,
|
||||
ObjectLiteral,
|
||||
QueryRunner,
|
||||
Repository,
|
||||
SelectQueryBuilder,
|
||||
} from 'typeorm';
|
||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
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 { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
import {
|
||||
OperationType,
|
||||
validateOperationIsPermittedOrThrow,
|
||||
} from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||
|
||||
export class WorkspaceEntityManager extends EntityManager {
|
||||
private readonly internalContext: WorkspaceInternalContext;
|
||||
readonly repositories: Map<string, Repository<any>>;
|
||||
declare connection: WorkspaceDataSource;
|
||||
|
||||
constructor(
|
||||
internalContext: WorkspaceInternalContext,
|
||||
connection: WorkspaceDataSource,
|
||||
queryRunner?: QueryRunner,
|
||||
) {
|
||||
super(connection, queryRunner);
|
||||
this.internalContext = internalContext;
|
||||
this.repositories = new Map();
|
||||
}
|
||||
|
||||
getFeatureFlagMap(): FeatureFlagMap {
|
||||
return this.connection.featureFlagMap;
|
||||
}
|
||||
|
||||
override getRepository<Entity extends ObjectLiteral>(
|
||||
target: EntityTarget<Entity>,
|
||||
shouldBypassPermissionChecks = false,
|
||||
roleId?: string,
|
||||
): WorkspaceRepository<Entity> {
|
||||
const dataSource = this.connection;
|
||||
|
||||
const repositoryKey = this.getRepositoryKey({
|
||||
target,
|
||||
dataSource,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
});
|
||||
const repoFromMap = this.repositories.get(repositoryKey);
|
||||
|
||||
if (repoFromMap) {
|
||||
return repoFromMap as WorkspaceRepository<Entity>;
|
||||
}
|
||||
|
||||
let objectPermissions = {};
|
||||
|
||||
if (roleId) {
|
||||
const objectPermissionsByRoleId = dataSource.permissionsPerRoleId;
|
||||
|
||||
objectPermissions = objectPermissionsByRoleId?.[roleId] ?? {};
|
||||
}
|
||||
|
||||
const newRepository = new WorkspaceRepository<Entity>(
|
||||
this.internalContext,
|
||||
target,
|
||||
this,
|
||||
dataSource.featureFlagMap,
|
||||
this.queryRunner,
|
||||
objectPermissions,
|
||||
shouldBypassPermissionChecks,
|
||||
);
|
||||
|
||||
this.repositories.set(repositoryKey, newRepository);
|
||||
|
||||
return newRepository;
|
||||
}
|
||||
|
||||
override createQueryBuilder<Entity extends ObjectLiteral>(
|
||||
entityClassOrQueryRunner?: EntityTarget<Entity> | QueryRunner,
|
||||
alias?: string,
|
||||
queryRunner?: QueryRunner,
|
||||
options: {
|
||||
shouldBypassPermissionChecks: boolean;
|
||||
roleId?: string;
|
||||
} = {
|
||||
shouldBypassPermissionChecks: false,
|
||||
},
|
||||
): SelectQueryBuilder<Entity> | WorkspaceSelectQueryBuilder<Entity> {
|
||||
let queryBuilder: SelectQueryBuilder<Entity>;
|
||||
|
||||
if (alias) {
|
||||
queryBuilder = super.createQueryBuilder(
|
||||
entityClassOrQueryRunner as EntityTarget<Entity>,
|
||||
alias as string,
|
||||
queryRunner as QueryRunner | undefined,
|
||||
);
|
||||
} else {
|
||||
queryBuilder = super.createQueryBuilder(
|
||||
entityClassOrQueryRunner as QueryRunner,
|
||||
);
|
||||
}
|
||||
|
||||
const featureFlagMap = this.getFeatureFlagMap();
|
||||
|
||||
const isPermissionsV2Enabled =
|
||||
featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
|
||||
|
||||
if (!isPermissionsV2Enabled) {
|
||||
return queryBuilder;
|
||||
} else {
|
||||
let objectPermissions = {};
|
||||
|
||||
if (options?.roleId) {
|
||||
const dataSource = this.connection as WorkspaceDataSource;
|
||||
const objectPermissionsByRoleId = dataSource.permissionsPerRoleId;
|
||||
|
||||
objectPermissions = objectPermissionsByRoleId?.[options.roleId] ?? {};
|
||||
}
|
||||
|
||||
return new WorkspaceSelectQueryBuilder(
|
||||
queryBuilder,
|
||||
objectPermissions,
|
||||
this.internalContext,
|
||||
options?.shouldBypassPermissionChecks ?? false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
override insert<Entity extends ObjectLiteral>(
|
||||
target: EntityTarget<Entity>,
|
||||
entityOrEntities:
|
||||
| QueryDeepPartialEntity<Entity>
|
||||
| QueryDeepPartialEntity<Entity>[],
|
||||
options?: {
|
||||
shouldBypassPermissionChecks?: boolean;
|
||||
objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||
},
|
||||
): Promise<InsertResult> {
|
||||
this.validatePermissions(target, 'insert', options);
|
||||
|
||||
return super.insert(target, entityOrEntities);
|
||||
}
|
||||
|
||||
override upsert<Entity extends ObjectLiteral>(
|
||||
target: EntityTarget<Entity>,
|
||||
entityOrEntities:
|
||||
| QueryDeepPartialEntity<Entity>
|
||||
| QueryDeepPartialEntity<Entity>[],
|
||||
conflictPathsOrOptions: string[] | UpsertOptions<Entity>,
|
||||
options?: {
|
||||
shouldBypassPermissionChecks?: boolean;
|
||||
objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||
},
|
||||
): Promise<InsertResult> {
|
||||
this.validatePermissions(target, 'update', options);
|
||||
|
||||
return super.upsert(target, entityOrEntities, conflictPathsOrOptions);
|
||||
}
|
||||
|
||||
private getRepositoryKey({
|
||||
target,
|
||||
dataSource,
|
||||
roleId,
|
||||
shouldBypassPermissionChecks,
|
||||
}: {
|
||||
target: EntityTarget<any>;
|
||||
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}`;
|
||||
}
|
||||
|
||||
private validatePermissions<Entity extends ObjectLiteral>(
|
||||
target: EntityTarget<Entity>,
|
||||
operationType: OperationType,
|
||||
options?: {
|
||||
shouldBypassPermissionChecks?: boolean;
|
||||
objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||
},
|
||||
): void {
|
||||
const featureFlagMap = this.getFeatureFlagMap();
|
||||
|
||||
const isPermissionsV2Enabled =
|
||||
featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
|
||||
|
||||
if (!isPermissionsV2Enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (options?.shouldBypassPermissionChecks === true) {
|
||||
return;
|
||||
}
|
||||
|
||||
validateOperationIsPermittedOrThrow({
|
||||
entityName: this.extractTargetNameSingularFromEntityTarget(target),
|
||||
operationType,
|
||||
objectRecordsPermissions: options?.objectRecordsPermissions ?? {},
|
||||
objectMetadataMaps: this.internalContext.objectMetadataMaps,
|
||||
});
|
||||
}
|
||||
|
||||
private extractTargetNameSingularFromEntityTarget(
|
||||
target: EntityTarget<any>,
|
||||
): string {
|
||||
return this.connection.getMetadata(target).name;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
import { QueryRunner } from 'typeorm';
|
||||
|
||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
|
||||
interface WorkspaceQueryRunner extends Omit<QueryRunner, 'manager'> {
|
||||
manager: WorkspaceEntityManager;
|
||||
}
|
||||
|
||||
export { WorkspaceQueryRunner };
|
||||
@ -18,21 +18,27 @@ const getTargetEntityAndOperationType = (expressionMap: QueryExpressionMap) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const validateQueryIsPermittedOrThrow = (
|
||||
expressionMap: QueryExpressionMap,
|
||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
) => {
|
||||
if (shouldBypassPermissionChecks) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { mainEntity, operationType } =
|
||||
getTargetEntityAndOperationType(expressionMap);
|
||||
export type OperationType =
|
||||
| 'select'
|
||||
| 'insert'
|
||||
| 'update'
|
||||
| 'delete'
|
||||
| 'restore'
|
||||
| 'soft-delete';
|
||||
|
||||
export const validateOperationIsPermittedOrThrow = ({
|
||||
entityName,
|
||||
operationType,
|
||||
objectRecordsPermissions,
|
||||
objectMetadataMaps,
|
||||
}: {
|
||||
entityName: string;
|
||||
operationType: OperationType;
|
||||
objectRecordsPermissions: ObjectRecordsPermissions;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
}) => {
|
||||
const objectMetadataIdForEntity =
|
||||
objectMetadataMaps.idByNameSingular[mainEntity];
|
||||
objectMetadataMaps.idByNameSingular[entityName];
|
||||
|
||||
const objectMetadataIsSystem =
|
||||
objectMetadataMaps.byId[objectMetadataIdForEntity]?.isSystem === true;
|
||||
@ -41,7 +47,7 @@ export const validateQueryIsPermittedOrThrow = (
|
||||
return;
|
||||
}
|
||||
|
||||
const permissionsForEntity = objectRecordsPermissions[mainEntity];
|
||||
const permissionsForEntity = objectRecordsPermissions[entityName];
|
||||
|
||||
switch (operationType) {
|
||||
case 'select':
|
||||
@ -85,3 +91,24 @@ export const validateQueryIsPermittedOrThrow = (
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const validateQueryIsPermittedOrThrow = (
|
||||
expressionMap: QueryExpressionMap,
|
||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
shouldBypassPermissionChecks: boolean,
|
||||
) => {
|
||||
if (shouldBypassPermissionChecks) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { mainEntity, operationType } =
|
||||
getTargetEntityAndOperationType(expressionMap);
|
||||
|
||||
validateOperationIsPermittedOrThrow({
|
||||
entityName: mainEntity,
|
||||
operationType: operationType as OperationType,
|
||||
objectRecordsPermissions,
|
||||
objectMetadataMaps,
|
||||
});
|
||||
};
|
||||
@ -9,7 +9,7 @@ import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util';
|
||||
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';
|
||||
|
||||
@ -3,7 +3,7 @@ import { InsertQueryBuilder, ObjectLiteral } from 'typeorm';
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util';
|
||||
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';
|
||||
|
||||
@ -4,7 +4,7 @@ import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util';
|
||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-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';
|
||||
@ -50,6 +50,48 @@ export class WorkspaceSelectQueryBuilder<
|
||||
return super.getMany();
|
||||
}
|
||||
|
||||
override getRawOne<U = any>(): Promise<U | undefined> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getRawOne();
|
||||
}
|
||||
|
||||
override getRawMany<U = any>(): Promise<U[]> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getRawMany();
|
||||
}
|
||||
|
||||
override getOne(): Promise<T | null> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getOne();
|
||||
}
|
||||
|
||||
override getOneOrFail(): Promise<T> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getOneOrFail();
|
||||
}
|
||||
|
||||
override getCount(): Promise<number> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getCount();
|
||||
}
|
||||
|
||||
override getExists(): Promise<boolean> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getExists();
|
||||
}
|
||||
|
||||
override getManyAndCount(): Promise<[T[], number]> {
|
||||
this.validatePermissions();
|
||||
|
||||
return super.getManyAndCount();
|
||||
}
|
||||
|
||||
override update(): WorkspaceUpdateQueryBuilder<T>;
|
||||
|
||||
override update(
|
||||
|
||||
@ -4,7 +4,7 @@ import { SoftDeleteQueryBuilder } from 'typeorm/query-builder/SoftDeleteQueryBui
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util';
|
||||
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';
|
||||
|
||||
@ -3,7 +3,7 @@ import { ObjectLiteral, UpdateQueryBuilder, UpdateResult } from 'typeorm';
|
||||
|
||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||
|
||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util';
|
||||
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';
|
||||
|
||||
@ -2,7 +2,6 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import {
|
||||
DeepPartial,
|
||||
DeleteResult,
|
||||
EntityManager,
|
||||
EntitySchema,
|
||||
EntityTarget,
|
||||
FindManyOptions,
|
||||
@ -27,6 +26,7 @@ import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/works
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||
import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage';
|
||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||
@ -39,10 +39,12 @@ export class WorkspaceRepository<
|
||||
private shouldBypassPermissionChecks: boolean;
|
||||
private featureFlagMap: FeatureFlagMap;
|
||||
private objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||
declare manager: WorkspaceEntityManager;
|
||||
|
||||
constructor(
|
||||
internalContext: WorkspaceInternalContext,
|
||||
target: EntityTarget<T>,
|
||||
manager: EntityManager,
|
||||
manager: WorkspaceEntityManager,
|
||||
featureFlagMap: FeatureFlagMap,
|
||||
queryRunner?: QueryRunner,
|
||||
objectRecordsPermissions?: ObjectRecordsPermissions,
|
||||
@ -53,6 +55,7 @@ export class WorkspaceRepository<
|
||||
this.featureFlagMap = featureFlagMap;
|
||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||
this.manager = manager;
|
||||
}
|
||||
|
||||
override createQueryBuilder<U extends T>(
|
||||
@ -87,7 +90,7 @@ export class WorkspaceRepository<
|
||||
*/
|
||||
override async find(
|
||||
options?: FindManyOptions<T>,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions(options);
|
||||
@ -99,7 +102,7 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findBy(
|
||||
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions({ where });
|
||||
@ -111,7 +114,7 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findAndCount(
|
||||
options?: FindManyOptions<T>,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<[T[], number]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions(options);
|
||||
@ -123,7 +126,7 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findAndCountBy(
|
||||
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<[T[], number]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions({ where });
|
||||
@ -138,7 +141,7 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findOne(
|
||||
options: FindOneOptions<T>,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions(options);
|
||||
@ -150,7 +153,7 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findOneBy(
|
||||
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions({ where });
|
||||
@ -162,7 +165,7 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findOneOrFail(
|
||||
options: FindOneOptions<T>,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions(options);
|
||||
@ -174,7 +177,7 @@ export class WorkspaceRepository<
|
||||
|
||||
override async findOneByOrFail(
|
||||
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions({ where });
|
||||
@ -193,31 +196,31 @@ export class WorkspaceRepository<
|
||||
override save<U extends DeepPartial<T>>(
|
||||
entities: U[],
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T[]>;
|
||||
|
||||
override save<U extends DeepPartial<T>>(
|
||||
entities: U[],
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<(U & T)[]>;
|
||||
|
||||
override save<U extends DeepPartial<T>>(
|
||||
entity: U,
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T>;
|
||||
|
||||
override save<U extends DeepPartial<T>>(
|
||||
entity: U,
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U & T>;
|
||||
|
||||
override async save<U extends DeepPartial<T>>(
|
||||
entityOrEntities: U | U[],
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U | U[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||
@ -249,19 +252,19 @@ export class WorkspaceRepository<
|
||||
override remove(
|
||||
entities: T[],
|
||||
options?: RemoveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T[]>;
|
||||
|
||||
override remove(
|
||||
entity: T,
|
||||
options?: RemoveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T>;
|
||||
|
||||
override async remove(
|
||||
entityOrEntities: T | T[],
|
||||
options?: RemoveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T | T[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||
@ -287,7 +290,7 @@ export class WorkspaceRepository<
|
||||
| ObjectId
|
||||
| ObjectId[]
|
||||
| FindOptionsWhere<T>,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<DeleteResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
@ -301,31 +304,31 @@ export class WorkspaceRepository<
|
||||
override softRemove<U extends DeepPartial<T>>(
|
||||
entities: U[],
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<T[]>;
|
||||
|
||||
override softRemove<U extends DeepPartial<T>>(
|
||||
entities: U[],
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<(U & T)[]>;
|
||||
|
||||
override softRemove<U extends DeepPartial<T>>(
|
||||
entity: U,
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U>;
|
||||
|
||||
override softRemove<U extends DeepPartial<T>>(
|
||||
entity: T,
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U & T>;
|
||||
|
||||
override async softRemove<U extends DeepPartial<T>>(
|
||||
entityOrEntities: U | U[],
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U | U[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||
@ -362,7 +365,7 @@ export class WorkspaceRepository<
|
||||
| ObjectId
|
||||
| ObjectId[]
|
||||
| FindOptionsWhere<T>,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
@ -379,31 +382,31 @@ export class WorkspaceRepository<
|
||||
override recover<U extends DeepPartial<T>>(
|
||||
entities: U,
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U>;
|
||||
|
||||
override recover<U extends DeepPartial<T>>(
|
||||
entities: U,
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<(U & T)[]>;
|
||||
|
||||
override recover<U extends DeepPartial<T>>(
|
||||
entity: U,
|
||||
options: SaveOptions & { reload: false },
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U>;
|
||||
|
||||
override recover<U extends DeepPartial<T>>(
|
||||
entity: U,
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U & T>;
|
||||
|
||||
override async recover<U extends DeepPartial<T>>(
|
||||
entityOrEntities: U | U[],
|
||||
options?: SaveOptions,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<U | U[]> {
|
||||
const manager = entityManager || this.manager;
|
||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||
@ -440,7 +443,7 @@ export class WorkspaceRepository<
|
||||
| ObjectId
|
||||
| ObjectId[]
|
||||
| FindOptionsWhere<T>,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
@ -456,12 +459,15 @@ export class WorkspaceRepository<
|
||||
*/
|
||||
override async insert(
|
||||
entity: QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[],
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<InsertResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
const formatedEntity = await this.formatData(entity);
|
||||
const result = await manager.insert(this.target, formatedEntity);
|
||||
const formattedEntity = await this.formatData(entity);
|
||||
const result = await manager.insert(this.target, formattedEntity, {
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
});
|
||||
const formattedResult = await this.formatResult(result.generatedMaps);
|
||||
|
||||
return {
|
||||
@ -486,7 +492,7 @@ export class WorkspaceRepository<
|
||||
| ObjectId[]
|
||||
| FindOptionsWhere<T>,
|
||||
partialEntity: QueryDeepPartialEntity<T>,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
@ -500,7 +506,7 @@ export class WorkspaceRepository<
|
||||
override async upsert(
|
||||
entityOrEntities: QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[],
|
||||
conflictPathsOrOptions: string[] | UpsertOptions<T>,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<InsertResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
|
||||
@ -510,6 +516,10 @@ export class WorkspaceRepository<
|
||||
this.target,
|
||||
formattedEntityOrEntities,
|
||||
conflictPathsOrOptions,
|
||||
{
|
||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||
},
|
||||
);
|
||||
|
||||
const formattedResult = await this.formatResult(result.generatedMaps);
|
||||
@ -526,7 +536,7 @@ export class WorkspaceRepository<
|
||||
*/
|
||||
override async exists(
|
||||
options?: FindManyOptions<T>,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<boolean> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions(options);
|
||||
@ -536,7 +546,7 @@ export class WorkspaceRepository<
|
||||
|
||||
override async existsBy(
|
||||
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<boolean> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions({ where });
|
||||
@ -549,7 +559,7 @@ export class WorkspaceRepository<
|
||||
*/
|
||||
override async count(
|
||||
options?: FindManyOptions<T>,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<number> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions(options);
|
||||
@ -559,7 +569,7 @@ export class WorkspaceRepository<
|
||||
|
||||
override async countBy(
|
||||
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<number> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions({ where });
|
||||
@ -573,7 +583,7 @@ export class WorkspaceRepository<
|
||||
override async sum(
|
||||
columnName: PickKeysByType<T, number>,
|
||||
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<number | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions({ where });
|
||||
@ -584,7 +594,7 @@ export class WorkspaceRepository<
|
||||
override async average(
|
||||
columnName: PickKeysByType<T, number>,
|
||||
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<number | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions({ where });
|
||||
@ -595,7 +605,7 @@ export class WorkspaceRepository<
|
||||
override async minimum(
|
||||
columnName: PickKeysByType<T, number>,
|
||||
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<number | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions({ where });
|
||||
@ -606,7 +616,7 @@ export class WorkspaceRepository<
|
||||
override async maximum(
|
||||
columnName: PickKeysByType<T, number>,
|
||||
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<number | null> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedOptions = await this.transformOptions({ where });
|
||||
@ -618,7 +628,7 @@ export class WorkspaceRepository<
|
||||
conditions: FindOptionsWhere<T>,
|
||||
propertyPath: string,
|
||||
value: number | string,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedConditions = await this.transformOptions({
|
||||
@ -637,7 +647,7 @@ export class WorkspaceRepository<
|
||||
conditions: FindOptionsWhere<T>,
|
||||
propertyPath: string,
|
||||
value: number | string,
|
||||
entityManager?: EntityManager,
|
||||
entityManager?: WorkspaceEntityManager,
|
||||
): Promise<UpdateResult> {
|
||||
const manager = entityManager || this.manager;
|
||||
const computedConditions = await this.transformOptions({
|
||||
|
||||
Reference in New Issue
Block a user