Closes https://github.com/twentyhq/core-team-issues/issues/747
This commit is contained in:
@ -0,0 +1,255 @@
|
|||||||
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
|
import { EntityManager } from 'typeorm';
|
||||||
|
|
||||||
|
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 { validateOperationIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||||
|
|
||||||
|
import { WorkspaceEntityManager } from './workspace-entity-manager';
|
||||||
|
|
||||||
|
jest.mock('src/engine/twenty-orm/repository/permissions.utils', () => ({
|
||||||
|
validateOperationIsPermittedOrThrow: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../repository/workspace-select-query-builder', () => ({
|
||||||
|
WorkspaceSelectQueryBuilder: jest.fn().mockImplementation(() => ({
|
||||||
|
where: jest.fn().mockReturnThis(),
|
||||||
|
getMany: jest.fn().mockResolvedValue([]),
|
||||||
|
getOne: jest.fn().mockResolvedValue(null),
|
||||||
|
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
|
||||||
|
execute: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ affected: 1, raw: [], generatedMaps: [] }),
|
||||||
|
setFindOptions: jest.fn().mockReturnThis(),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('WorkspaceEntityManager', () => {
|
||||||
|
let entityManager: WorkspaceEntityManager;
|
||||||
|
let mockInternalContext: WorkspaceInternalContext;
|
||||||
|
let mockDataSource: WorkspaceDataSource;
|
||||||
|
let mockPermissionOptions: {
|
||||||
|
shouldBypassPermissionChecks: boolean;
|
||||||
|
objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockInternalContext = {
|
||||||
|
workspaceId: 'test-workspace-id',
|
||||||
|
objectMetadataMaps: {
|
||||||
|
idByNameSingular: {},
|
||||||
|
},
|
||||||
|
featureFlagsMap: {
|
||||||
|
[FeatureFlagKey.IsPermissionsV2Enabled]: true,
|
||||||
|
},
|
||||||
|
} as WorkspaceInternalContext;
|
||||||
|
|
||||||
|
mockDataSource = {
|
||||||
|
featureFlagMap: {
|
||||||
|
[FeatureFlagKey.IsPermissionsV2Enabled]: true,
|
||||||
|
},
|
||||||
|
permissionsPerRoleId: {},
|
||||||
|
} as WorkspaceDataSource;
|
||||||
|
|
||||||
|
mockPermissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: {
|
||||||
|
'test-entity': {
|
||||||
|
canRead: true,
|
||||||
|
canUpdate: false,
|
||||||
|
canSoftDelete: false,
|
||||||
|
canDestroy: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock TypeORM connection methods
|
||||||
|
const mockWorkspaceDataSource = {
|
||||||
|
getMetadata: jest.fn().mockReturnValue({
|
||||||
|
name: 'test-entity',
|
||||||
|
columns: [],
|
||||||
|
relations: [],
|
||||||
|
findInheritanceMetadata: jest.fn(),
|
||||||
|
findColumnWithPropertyPath: jest.fn(),
|
||||||
|
}),
|
||||||
|
createQueryBuilder: jest.fn().mockReturnValue({
|
||||||
|
delete: jest.fn().mockReturnThis(),
|
||||||
|
from: jest.fn().mockReturnThis(),
|
||||||
|
where: jest.fn().mockReturnThis(),
|
||||||
|
execute: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ affected: 1, raw: [], generatedMaps: [] }),
|
||||||
|
getMany: jest.fn().mockResolvedValue([]),
|
||||||
|
getOne: jest.fn().mockResolvedValue(null),
|
||||||
|
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
|
||||||
|
update: jest.fn().mockReturnThis(),
|
||||||
|
softDelete: jest.fn().mockReturnThis(),
|
||||||
|
restore: jest.fn().mockReturnThis(),
|
||||||
|
}),
|
||||||
|
createQueryRunner: jest.fn().mockReturnValue({
|
||||||
|
connect: jest.fn(),
|
||||||
|
startTransaction: jest.fn(),
|
||||||
|
commitTransaction: jest.fn(),
|
||||||
|
rollbackTransaction: jest.fn(),
|
||||||
|
release: jest.fn(),
|
||||||
|
clearTable: jest.fn(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
entityManager = new WorkspaceEntityManager(
|
||||||
|
mockInternalContext,
|
||||||
|
mockDataSource,
|
||||||
|
);
|
||||||
|
|
||||||
|
Object.defineProperty(entityManager, 'connection', {
|
||||||
|
get: () => mockWorkspaceDataSource,
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.spyOn(entityManager as any, 'validatePermissions');
|
||||||
|
jest.spyOn(entityManager as any, 'createQueryBuilder');
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(entityManager as any, 'extractTargetNameSingularFromEntityTarget')
|
||||||
|
.mockImplementation((entityName: string) => {
|
||||||
|
return entityName;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock getFeatureFlagMap
|
||||||
|
jest.spyOn(entityManager as any, 'getFeatureFlagMap').mockReturnValue({
|
||||||
|
[FeatureFlagKey.IsPermissionsV2Enabled]: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mock typeORM's EntityManager methods
|
||||||
|
jest
|
||||||
|
.spyOn(EntityManager.prototype, 'save')
|
||||||
|
.mockImplementation(() => Promise.resolve({}));
|
||||||
|
jest
|
||||||
|
.spyOn(EntityManager.prototype, 'update')
|
||||||
|
.mockImplementation(() =>
|
||||||
|
Promise.resolve({ affected: 1, raw: [], generatedMaps: [] }),
|
||||||
|
);
|
||||||
|
jest
|
||||||
|
.spyOn(EntityManager.prototype, 'restore')
|
||||||
|
.mockImplementation(() =>
|
||||||
|
Promise.resolve({ affected: 1, raw: [], generatedMaps: [] }),
|
||||||
|
);
|
||||||
|
jest
|
||||||
|
.spyOn(EntityManager.prototype, 'clear')
|
||||||
|
.mockImplementation(() => Promise.resolve());
|
||||||
|
jest
|
||||||
|
.spyOn(EntityManager.prototype, 'preload')
|
||||||
|
.mockImplementation(() => Promise.resolve({}));
|
||||||
|
|
||||||
|
// Mock metadata methods
|
||||||
|
const mockMetadata = {
|
||||||
|
hasAllPrimaryKeys: jest.fn().mockReturnValue(true),
|
||||||
|
columns: [],
|
||||||
|
relations: [],
|
||||||
|
findInheritanceMetadata: jest.fn(),
|
||||||
|
findColumnWithPropertyPath: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update mockWorkspaceDataSource to include metadata
|
||||||
|
mockWorkspaceDataSource.getMetadata = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(mockMetadata);
|
||||||
|
|
||||||
|
// Reset the mock before each test
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Query Method', () => {
|
||||||
|
it('should call validatePermissions and validateOperationIsPermittedOrThrow for find', async () => {
|
||||||
|
await entityManager.find('test-entity', {}, mockPermissionOptions);
|
||||||
|
|
||||||
|
expect(entityManager.createQueryBuilder).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
mockPermissionOptions,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('should throw error for query', async () => {
|
||||||
|
expect(() => entityManager.query('SELECT * FROM test')).toThrow(
|
||||||
|
'Method not allowed.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Save Methods', () => {
|
||||||
|
it('should call validatePermissions and validateOperationIsPermittedOrThrow for save', async () => {
|
||||||
|
await entityManager.save(
|
||||||
|
'test-entity',
|
||||||
|
{},
|
||||||
|
{ reload: false },
|
||||||
|
mockPermissionOptions,
|
||||||
|
);
|
||||||
|
expect(entityManager['validatePermissions']).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
'update',
|
||||||
|
mockPermissionOptions,
|
||||||
|
);
|
||||||
|
expect(validateOperationIsPermittedOrThrow).toHaveBeenCalledWith({
|
||||||
|
entityName: 'test-entity',
|
||||||
|
operationType: 'update',
|
||||||
|
objectMetadataMaps: mockInternalContext.objectMetadataMaps,
|
||||||
|
objectRecordsPermissions:
|
||||||
|
mockPermissionOptions.objectRecordsPermissions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Update Methods', () => {
|
||||||
|
it('should call validatePermissions and validateOperationIsPermittedOrThrow for update', async () => {
|
||||||
|
await entityManager.update('test-entity', {}, {}, mockPermissionOptions);
|
||||||
|
expect(entityManager['validatePermissions']).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
'update',
|
||||||
|
mockPermissionOptions,
|
||||||
|
);
|
||||||
|
expect(validateOperationIsPermittedOrThrow).toHaveBeenCalledWith({
|
||||||
|
entityName: 'test-entity',
|
||||||
|
operationType: 'update',
|
||||||
|
objectMetadataMaps: mockInternalContext.objectMetadataMaps,
|
||||||
|
objectRecordsPermissions:
|
||||||
|
mockPermissionOptions.objectRecordsPermissions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Other Methods', () => {
|
||||||
|
it('should call validatePermissions and validateOperationIsPermittedOrThrow for clear', async () => {
|
||||||
|
await entityManager.clear('test-entity', mockPermissionOptions);
|
||||||
|
expect(entityManager['validatePermissions']).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
'delete',
|
||||||
|
mockPermissionOptions,
|
||||||
|
);
|
||||||
|
expect(validateOperationIsPermittedOrThrow).toHaveBeenCalledWith({
|
||||||
|
entityName: 'test-entity',
|
||||||
|
operationType: 'delete',
|
||||||
|
objectMetadataMaps: mockInternalContext.objectMetadataMaps,
|
||||||
|
objectRecordsPermissions:
|
||||||
|
mockPermissionOptions.objectRecordsPermissions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call validatePermissions and validateOperationIsPermittedOrThrow for preload', async () => {
|
||||||
|
await entityManager.preload('test-entity', {}, mockPermissionOptions);
|
||||||
|
expect(entityManager['validatePermissions']).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
'select',
|
||||||
|
mockPermissionOptions,
|
||||||
|
);
|
||||||
|
expect(validateOperationIsPermittedOrThrow).toHaveBeenCalledWith({
|
||||||
|
entityName: 'test-entity',
|
||||||
|
operationType: 'select',
|
||||||
|
objectMetadataMaps: mockInternalContext.objectMetadataMaps,
|
||||||
|
objectRecordsPermissions:
|
||||||
|
mockPermissionOptions.objectRecordsPermissions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -1,15 +1,28 @@
|
|||||||
|
import { Entity } from '@microsoft/microsoft-graph-types';
|
||||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import {
|
import {
|
||||||
|
DeleteResult,
|
||||||
EntityManager,
|
EntityManager,
|
||||||
EntityTarget,
|
EntityTarget,
|
||||||
FindManyOptions,
|
FindManyOptions,
|
||||||
|
FindOneOptions,
|
||||||
|
FindOptionsWhere,
|
||||||
InsertResult,
|
InsertResult,
|
||||||
|
ObjectId,
|
||||||
ObjectLiteral,
|
ObjectLiteral,
|
||||||
QueryRunner,
|
QueryRunner,
|
||||||
|
RemoveOptions,
|
||||||
Repository,
|
Repository,
|
||||||
|
SaveOptions,
|
||||||
SelectQueryBuilder,
|
SelectQueryBuilder,
|
||||||
|
TypeORMError,
|
||||||
|
UpdateResult,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
import { DeepPartial } from 'typeorm/common/DeepPartial';
|
||||||
|
import { PickKeysByType } from 'typeorm/common/PickKeysByType';
|
||||||
|
import { EntityNotFoundError } from 'typeorm/error/EntityNotFoundError';
|
||||||
|
import { FindOptionsUtils } from 'typeorm/find-options/FindOptionsUtils';
|
||||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||||
import { UpsertOptions } from 'typeorm/repository/UpsertOptions';
|
import { UpsertOptions } from 'typeorm/repository/UpsertOptions';
|
||||||
|
|
||||||
@ -29,6 +42,11 @@ import {
|
|||||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
|
|
||||||
|
type PermissionOptions = {
|
||||||
|
shouldBypassPermissionChecks?: boolean;
|
||||||
|
objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||||
|
};
|
||||||
|
|
||||||
export class WorkspaceEntityManager extends EntityManager {
|
export class WorkspaceEntityManager extends EntityManager {
|
||||||
private readonly internalContext: WorkspaceInternalContext;
|
private readonly internalContext: WorkspaceInternalContext;
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -117,10 +135,11 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
alias?: string,
|
alias?: string,
|
||||||
queryRunner?: QueryRunner,
|
queryRunner?: QueryRunner,
|
||||||
options: {
|
options: {
|
||||||
shouldBypassPermissionChecks: boolean;
|
shouldBypassPermissionChecks?: boolean;
|
||||||
roleId?: string;
|
objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||||
} = {
|
} = {
|
||||||
shouldBypassPermissionChecks: false,
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: {},
|
||||||
},
|
},
|
||||||
): SelectQueryBuilder<Entity> | WorkspaceSelectQueryBuilder<Entity> {
|
): SelectQueryBuilder<Entity> | WorkspaceSelectQueryBuilder<Entity> {
|
||||||
let queryBuilder: SelectQueryBuilder<Entity>;
|
let queryBuilder: SelectQueryBuilder<Entity>;
|
||||||
@ -145,50 +164,23 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
if (!isPermissionsV2Enabled) {
|
if (!isPermissionsV2Enabled) {
|
||||||
return queryBuilder;
|
return queryBuilder;
|
||||||
} else {
|
} else {
|
||||||
let objectPermissions = {};
|
|
||||||
|
|
||||||
if (options?.roleId) {
|
|
||||||
const dataSource = this.connection as WorkspaceDataSource;
|
|
||||||
const objectPermissionsByRoleId = dataSource.permissionsPerRoleId;
|
|
||||||
|
|
||||||
objectPermissions = objectPermissionsByRoleId?.[options.roleId] ?? {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return new WorkspaceSelectQueryBuilder(
|
return new WorkspaceSelectQueryBuilder(
|
||||||
queryBuilder,
|
queryBuilder,
|
||||||
objectPermissions,
|
options?.objectRecordsPermissions ?? {},
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
options?.shouldBypassPermissionChecks ?? false,
|
options?.shouldBypassPermissionChecks ?? false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override find<Entity extends ObjectLiteral>(
|
|
||||||
target: EntityTarget<Entity>,
|
|
||||||
options?: FindManyOptions<Entity>,
|
|
||||||
permissionOptions?: {
|
|
||||||
shouldBypassPermissionChecks?: boolean;
|
|
||||||
objectRecordsPermissions?: ObjectRecordsPermissions;
|
|
||||||
},
|
|
||||||
): Promise<Entity[]> {
|
|
||||||
this.validatePermissions(target, 'select', permissionOptions);
|
|
||||||
|
|
||||||
return super.find(target, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
override insert<Entity extends ObjectLiteral>(
|
override insert<Entity extends ObjectLiteral>(
|
||||||
target: EntityTarget<Entity>,
|
target: EntityTarget<Entity>,
|
||||||
entityOrEntities:
|
entity: QueryDeepPartialEntity<Entity> | QueryDeepPartialEntity<Entity>[],
|
||||||
| QueryDeepPartialEntity<Entity>
|
permissionOptions?: PermissionOptions,
|
||||||
| QueryDeepPartialEntity<Entity>[],
|
|
||||||
permissionOptions?: {
|
|
||||||
shouldBypassPermissionChecks?: boolean;
|
|
||||||
objectRecordsPermissions?: ObjectRecordsPermissions;
|
|
||||||
},
|
|
||||||
): Promise<InsertResult> {
|
): Promise<InsertResult> {
|
||||||
this.validatePermissions(target, 'insert', permissionOptions);
|
this.validatePermissions(target, 'insert', permissionOptions);
|
||||||
|
|
||||||
return super.insert(target, entityOrEntities);
|
return super.insert(target, entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
override upsert<Entity extends ObjectLiteral>(
|
override upsert<Entity extends ObjectLiteral>(
|
||||||
@ -207,6 +199,141 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
return super.upsert(target, entityOrEntities, conflictPathsOrOptions);
|
return super.upsert(target, entityOrEntities, conflictPathsOrOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override update<Entity extends ObjectLiteral>(
|
||||||
|
target: EntityTarget<Entity>,
|
||||||
|
criteria:
|
||||||
|
| string
|
||||||
|
| string[]
|
||||||
|
| number
|
||||||
|
| number[]
|
||||||
|
| Date
|
||||||
|
| Date[]
|
||||||
|
| ObjectId
|
||||||
|
| ObjectId[]
|
||||||
|
| unknown,
|
||||||
|
partialEntity: QueryDeepPartialEntity<Entity>,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
|
this.validatePermissions(target, 'update', permissionOptions);
|
||||||
|
|
||||||
|
return super.update(target, criteria, partialEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
override save<Entity extends ObjectLiteral>(
|
||||||
|
entities: Entity[],
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity[]>;
|
||||||
|
|
||||||
|
override save<Entity extends ObjectLiteral>(
|
||||||
|
entity: Entity,
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity>;
|
||||||
|
|
||||||
|
override save<Entity, T extends DeepPartial<Entity>>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
entities: T[],
|
||||||
|
options: SaveOptions & {
|
||||||
|
reload: false;
|
||||||
|
},
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<T[]>;
|
||||||
|
|
||||||
|
override save<Entity, T extends DeepPartial<Entity>>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
entities: T[],
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<(T & Entity)[]>;
|
||||||
|
|
||||||
|
override save<Entity, T extends DeepPartial<Entity>>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
entity: T,
|
||||||
|
options: SaveOptions & {
|
||||||
|
reload: false;
|
||||||
|
},
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<T>;
|
||||||
|
|
||||||
|
override save<Entity, T extends DeepPartial<Entity>>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
entity: T,
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<T>;
|
||||||
|
|
||||||
|
override save<Entity extends ObjectLiteral, T extends DeepPartial<Entity>>(
|
||||||
|
targetOrEntity: EntityTarget<Entity> | Entity | Entity[],
|
||||||
|
entityOrMaybeOptions:
|
||||||
|
| T
|
||||||
|
| T[]
|
||||||
|
| SaveOptions
|
||||||
|
| (SaveOptions & { reload: false }),
|
||||||
|
maybeOptionsOrMaybePermissionOptions?:
|
||||||
|
| PermissionOptions
|
||||||
|
| SaveOptions
|
||||||
|
| (SaveOptions & { reload: false }),
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<(T & Entity) | (T & Entity)[] | Entity | Entity[]> {
|
||||||
|
const permissionOptionsFromArgs =
|
||||||
|
maybeOptionsOrMaybePermissionOptions &&
|
||||||
|
('shouldBypassPermissionChecks' in maybeOptionsOrMaybePermissionOptions ||
|
||||||
|
'objectRecordsPermissions' in maybeOptionsOrMaybePermissionOptions)
|
||||||
|
? maybeOptionsOrMaybePermissionOptions
|
||||||
|
: permissionOptions;
|
||||||
|
|
||||||
|
this.validatePermissions(
|
||||||
|
targetOrEntity,
|
||||||
|
'update',
|
||||||
|
permissionOptionsFromArgs,
|
||||||
|
);
|
||||||
|
|
||||||
|
const target =
|
||||||
|
arguments.length > 1 &&
|
||||||
|
(typeof targetOrEntity === 'function' ||
|
||||||
|
typeof targetOrEntity === 'string')
|
||||||
|
? (targetOrEntity as EntityTarget<Entity>)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const entityOrEntities = target
|
||||||
|
? (entityOrMaybeOptions as T | T[])
|
||||||
|
: (targetOrEntity as Entity | Entity[]);
|
||||||
|
|
||||||
|
const options = target
|
||||||
|
? (maybeOptionsOrMaybePermissionOptions as SaveOptions | undefined)
|
||||||
|
: (entityOrMaybeOptions as SaveOptions | undefined);
|
||||||
|
|
||||||
|
if (isDefined(target)) {
|
||||||
|
let entity: T | undefined;
|
||||||
|
let entities: T[] | undefined;
|
||||||
|
|
||||||
|
if (Array.isArray(entityOrEntities)) {
|
||||||
|
entities = entityOrEntities as T[];
|
||||||
|
|
||||||
|
return super.save(target, entities, options);
|
||||||
|
} else {
|
||||||
|
entity = entityOrEntities as T;
|
||||||
|
|
||||||
|
return super.save(target, entity, options);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return super.save(entityOrEntities as Entity | Entity[], options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override increment<Entity extends ObjectLiteral>(
|
||||||
|
target: EntityTarget<Entity>,
|
||||||
|
criteria: unknown,
|
||||||
|
propertyPath: string,
|
||||||
|
value: number | string,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
|
this.validatePermissions(target, 'update', permissionOptions);
|
||||||
|
|
||||||
|
return super.increment(target, criteria, propertyPath, value);
|
||||||
|
}
|
||||||
|
|
||||||
private getRepositoryKey({
|
private getRepositoryKey({
|
||||||
target,
|
target,
|
||||||
dataSource,
|
dataSource,
|
||||||
@ -214,7 +341,7 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
shouldBypassPermissionChecks,
|
shouldBypassPermissionChecks,
|
||||||
}: {
|
}: {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
target: EntityTarget<any>;
|
target: EntityTarget<unknown>;
|
||||||
dataSource: WorkspaceDataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
shouldBypassPermissionChecks: boolean;
|
shouldBypassPermissionChecks: boolean;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
@ -233,8 +360,8 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
: `${repositoryPrefix}${roleIdSuffix}${rolesPermissionsVersionSuffix}${featureFlagMapVersionSuffix}`;
|
: `${repositoryPrefix}${roleIdSuffix}${rolesPermissionsVersionSuffix}${featureFlagMapVersionSuffix}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private validatePermissions<Entity extends ObjectLiteral>(
|
validatePermissions<Entity extends ObjectLiteral>(
|
||||||
target: EntityTarget<Entity>,
|
target: EntityTarget<Entity> | Entity,
|
||||||
operationType: OperationType,
|
operationType: OperationType,
|
||||||
permissionOptions?: {
|
permissionOptions?: {
|
||||||
shouldBypassPermissionChecks?: boolean;
|
shouldBypassPermissionChecks?: boolean;
|
||||||
@ -254,8 +381,13 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const entityName =
|
||||||
|
typeof target === 'function' || typeof target === 'string'
|
||||||
|
? this.extractTargetNameSingularFromEntityTarget(target)
|
||||||
|
: this.extractTargetNameSingularFromEntity(target);
|
||||||
|
|
||||||
validateOperationIsPermittedOrThrow({
|
validateOperationIsPermittedOrThrow({
|
||||||
entityName: this.extractTargetNameSingularFromEntityTarget(target),
|
entityName,
|
||||||
operationType,
|
operationType,
|
||||||
objectRecordsPermissions:
|
objectRecordsPermissions:
|
||||||
permissionOptions?.objectRecordsPermissions ?? {},
|
permissionOptions?.objectRecordsPermissions ?? {},
|
||||||
@ -264,9 +396,706 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private extractTargetNameSingularFromEntityTarget(
|
private extractTargetNameSingularFromEntityTarget(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
target: EntityTarget<unknown>,
|
||||||
target: EntityTarget<any>,
|
|
||||||
): string {
|
): string {
|
||||||
return this.connection.getMetadata(target).name;
|
return this.connection.getMetadata(target).name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
private extractTargetNameSingularFromEntity(entity: any): string {
|
||||||
|
return this.connection.getMetadata(entity.constructor).name;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Forbidden methods
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
override query<T = any>(_query: string, _parameters?: any[]): Promise<T> {
|
||||||
|
throw new Error('Method not allowed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not in use methods - duplicated from TypeORM's EntityManager to use our createQueryBuilder
|
||||||
|
|
||||||
|
override find<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
options?: FindManyOptions<Entity>,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity[]> {
|
||||||
|
const metadata = this.connection.getMetadata(entityClass);
|
||||||
|
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
entityClass,
|
||||||
|
FindOptionsUtils.extractFindManyOptionsAlias(options) || metadata.name,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.setFindOptions(options || {})
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
override findBy<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity[]> {
|
||||||
|
const metadata = this.connection.getMetadata(entityClass);
|
||||||
|
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
entityClass,
|
||||||
|
metadata.name,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.setFindOptions({ where: where })
|
||||||
|
.getMany();
|
||||||
|
}
|
||||||
|
|
||||||
|
override findOne<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
options: FindOneOptions<Entity>,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity | null> {
|
||||||
|
const metadata = this.connection.getMetadata(entityClass);
|
||||||
|
// prepare alias for built query
|
||||||
|
let alias = metadata.name;
|
||||||
|
|
||||||
|
if (options && options.join) {
|
||||||
|
alias = options.join.alias;
|
||||||
|
}
|
||||||
|
if (!options.where) {
|
||||||
|
throw new Error(
|
||||||
|
`You must provide selection conditions in order to find a single row.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create query builder and apply find options
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
entityClass,
|
||||||
|
alias,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.setFindOptions({
|
||||||
|
...options,
|
||||||
|
take: 1,
|
||||||
|
})
|
||||||
|
.getOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
override findOneBy<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity | null> {
|
||||||
|
const metadata = this.connection.getMetadata(entityClass);
|
||||||
|
|
||||||
|
// create query builder and apply find options
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
entityClass,
|
||||||
|
metadata.name,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.setFindOptions({
|
||||||
|
where,
|
||||||
|
take: 1,
|
||||||
|
})
|
||||||
|
.getOne();
|
||||||
|
}
|
||||||
|
|
||||||
|
override findAndCount<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
options?: FindManyOptions<Entity>,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<[Entity[], number]> {
|
||||||
|
const metadata = this.connection.getMetadata(entityClass);
|
||||||
|
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
entityClass,
|
||||||
|
FindOptionsUtils.extractFindManyOptionsAlias(options) || metadata.name,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.setFindOptions(options || {})
|
||||||
|
.getManyAndCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
override findAndCountBy<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<[Entity[], number]> {
|
||||||
|
const metadata = this.connection.getMetadata(entityClass);
|
||||||
|
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
entityClass,
|
||||||
|
metadata.name,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.setFindOptions({ where })
|
||||||
|
.getManyAndCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
override findOneOrFail<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
options: FindOneOptions<Entity>,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity> {
|
||||||
|
return this.findOne(entityClass, options, permissionOptions).then(
|
||||||
|
(value) => {
|
||||||
|
if (value === null) {
|
||||||
|
return Promise.reject(new EntityNotFoundError(entityClass, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override findOneByOrFail<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity> {
|
||||||
|
return this.findOneBy(entityClass, where, permissionOptions).then(
|
||||||
|
(value) => {
|
||||||
|
if (value === null) {
|
||||||
|
return Promise.reject(new EntityNotFoundError(entityClass, where));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(value);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override delete<Entity extends ObjectLiteral>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
criteria: unknown,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<DeleteResult> {
|
||||||
|
this.validatePermissions(targetOrEntity, 'delete', permissionOptions);
|
||||||
|
|
||||||
|
return super.delete(targetOrEntity, criteria);
|
||||||
|
}
|
||||||
|
|
||||||
|
override remove<Entity>(
|
||||||
|
entity: Entity,
|
||||||
|
options?: RemoveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity>;
|
||||||
|
|
||||||
|
override remove<Entity>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
entity: Entity,
|
||||||
|
options?: RemoveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity>;
|
||||||
|
|
||||||
|
override remove<Entity>(
|
||||||
|
entity: Entity[],
|
||||||
|
options?: RemoveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity>;
|
||||||
|
|
||||||
|
override remove<Entity>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
entity: Entity[],
|
||||||
|
options?: RemoveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity[]>;
|
||||||
|
|
||||||
|
override remove<Entity extends ObjectLiteral>(
|
||||||
|
targetOrEntity: EntityTarget<Entity> | Entity[] | Entity,
|
||||||
|
entityOrMaybeOptions: Entity | Entity[] | RemoveOptions,
|
||||||
|
maybeOptionsOrMaybePermissionOptions?: RemoveOptions | PermissionOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity | Entity[]> {
|
||||||
|
const permissionOptionsFromArgs =
|
||||||
|
maybeOptionsOrMaybePermissionOptions &&
|
||||||
|
('shouldBypassPermissionChecks' in maybeOptionsOrMaybePermissionOptions ||
|
||||||
|
'objectRecordsPermissions' in maybeOptionsOrMaybePermissionOptions)
|
||||||
|
? (maybeOptionsOrMaybePermissionOptions as PermissionOptions)
|
||||||
|
: permissionOptions;
|
||||||
|
|
||||||
|
this.validatePermissions(
|
||||||
|
targetOrEntity,
|
||||||
|
'delete',
|
||||||
|
permissionOptionsFromArgs,
|
||||||
|
);
|
||||||
|
|
||||||
|
const target =
|
||||||
|
typeof targetOrEntity === 'function' || typeof targetOrEntity === 'string'
|
||||||
|
? (targetOrEntity as EntityTarget<Entity>)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const entityOrEntities = target
|
||||||
|
? (entityOrMaybeOptions as Entity | Entity[])
|
||||||
|
: (targetOrEntity as Entity | Entity[]);
|
||||||
|
|
||||||
|
const options = target
|
||||||
|
? (maybeOptionsOrMaybePermissionOptions as RemoveOptions | undefined)
|
||||||
|
: (entityOrMaybeOptions as RemoveOptions);
|
||||||
|
|
||||||
|
if (isDefined(target)) {
|
||||||
|
if (Array.isArray(entityOrEntities)) {
|
||||||
|
return super.remove(target, entityOrEntities as Entity[], options);
|
||||||
|
} else {
|
||||||
|
return super.remove(target, entityOrEntities as Entity, options);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return super.remove(entityOrEntities as Entity | Entity[], options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override softDelete<Entity extends ObjectLiteral>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
criteria: unknown,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
|
// if user passed empty criteria or empty list of criterias, then throw an error
|
||||||
|
if (
|
||||||
|
criteria === undefined ||
|
||||||
|
criteria === null ||
|
||||||
|
criteria === '' ||
|
||||||
|
(Array.isArray(criteria) && criteria.length === 0)
|
||||||
|
) {
|
||||||
|
return Promise.reject(
|
||||||
|
new TypeORMError(
|
||||||
|
`Empty criteria(s) are not allowed for the softDelete method.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof criteria === 'string' ||
|
||||||
|
typeof criteria === 'number' ||
|
||||||
|
criteria instanceof Date ||
|
||||||
|
Array.isArray(criteria)
|
||||||
|
) {
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.softDelete()
|
||||||
|
.from(targetOrEntity)
|
||||||
|
.whereInIds(criteria)
|
||||||
|
.execute();
|
||||||
|
} else {
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.softDelete()
|
||||||
|
.from(targetOrEntity)
|
||||||
|
.where(criteria)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override softRemove<Entity extends ObjectLiteral>(
|
||||||
|
entities: Entity[],
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity[]>;
|
||||||
|
|
||||||
|
override softRemove<Entity extends ObjectLiteral>(
|
||||||
|
entities: Entity,
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity>;
|
||||||
|
|
||||||
|
override softRemove<
|
||||||
|
Entity extends ObjectLiteral,
|
||||||
|
T extends DeepPartial<Entity>,
|
||||||
|
>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
entities: T[],
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<T[]>;
|
||||||
|
|
||||||
|
override softRemove<
|
||||||
|
Entity extends ObjectLiteral,
|
||||||
|
T extends DeepPartial<Entity>,
|
||||||
|
>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
entities: T,
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<T>;
|
||||||
|
|
||||||
|
override async softRemove<
|
||||||
|
Entity extends ObjectLiteral,
|
||||||
|
T extends DeepPartial<Entity>,
|
||||||
|
>(
|
||||||
|
targetOrEntityOrEntities: Entity | Entity[] | EntityTarget<Entity>,
|
||||||
|
entitiesOrMaybeOptions: T | T[] | SaveOptions,
|
||||||
|
maybeOptionsOrMaybePermissionOptions?: SaveOptions | PermissionOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity | Entity[] | T | T[]> {
|
||||||
|
const permissionOptionsFromArgs =
|
||||||
|
maybeOptionsOrMaybePermissionOptions &&
|
||||||
|
('shouldBypassPermissionChecks' in maybeOptionsOrMaybePermissionOptions ||
|
||||||
|
'objectRecordsPermissions' in maybeOptionsOrMaybePermissionOptions)
|
||||||
|
? (maybeOptionsOrMaybePermissionOptions as PermissionOptions)
|
||||||
|
: permissionOptions;
|
||||||
|
|
||||||
|
this.validatePermissions(
|
||||||
|
targetOrEntityOrEntities,
|
||||||
|
'soft-delete',
|
||||||
|
permissionOptionsFromArgs,
|
||||||
|
);
|
||||||
|
|
||||||
|
const target =
|
||||||
|
typeof targetOrEntityOrEntities === 'function' ||
|
||||||
|
typeof targetOrEntityOrEntities === 'string'
|
||||||
|
? (targetOrEntityOrEntities as EntityTarget<Entity>)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const entityOrEntities = target
|
||||||
|
? (entitiesOrMaybeOptions as T | T[])
|
||||||
|
: (targetOrEntityOrEntities as Entity | Entity[]);
|
||||||
|
|
||||||
|
const options = target
|
||||||
|
? (maybeOptionsOrMaybePermissionOptions as SaveOptions | undefined)
|
||||||
|
: (entitiesOrMaybeOptions as SaveOptions);
|
||||||
|
|
||||||
|
if (isDefined(target)) {
|
||||||
|
if (Array.isArray(entityOrEntities)) {
|
||||||
|
return super.softRemove(target, entityOrEntities as T[], options);
|
||||||
|
} else {
|
||||||
|
return super.softRemove(target, entityOrEntities as T, options);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Array.isArray(entityOrEntities)) {
|
||||||
|
return super.softRemove(entityOrEntities as Entity | Entity[], options);
|
||||||
|
} else {
|
||||||
|
return super.softRemove(entityOrEntities as Entity, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override recover<Entity>(
|
||||||
|
entities: Entity[],
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity[]>;
|
||||||
|
|
||||||
|
override recover<Entity>(
|
||||||
|
entity: Entity,
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity>;
|
||||||
|
|
||||||
|
override recover<Entity, T extends DeepPartial<Entity>>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
entities: T[],
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<T[]>;
|
||||||
|
|
||||||
|
override recover<Entity, T extends DeepPartial<Entity>>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
entity: T,
|
||||||
|
options?: SaveOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<T>;
|
||||||
|
|
||||||
|
override recover<Entity extends ObjectLiteral, T extends DeepPartial<Entity>>(
|
||||||
|
targetOrEntityOrEntities: EntityTarget<Entity> | Entity | Entity[],
|
||||||
|
entityOrEntitiesOrMaybeOptions: T | T[] | SaveOptions,
|
||||||
|
maybeOptionsOrMaybePermissionOptions?: SaveOptions | PermissionOptions,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity | Entity[] | T | T[]> {
|
||||||
|
const permissionOptionsFromArgs =
|
||||||
|
maybeOptionsOrMaybePermissionOptions &&
|
||||||
|
('shouldBypassPermissionChecks' in maybeOptionsOrMaybePermissionOptions ||
|
||||||
|
'objectRecordsPermissions' in maybeOptionsOrMaybePermissionOptions)
|
||||||
|
? (maybeOptionsOrMaybePermissionOptions as PermissionOptions)
|
||||||
|
: permissionOptions;
|
||||||
|
|
||||||
|
this.validatePermissions(
|
||||||
|
targetOrEntityOrEntities,
|
||||||
|
'restore',
|
||||||
|
permissionOptionsFromArgs,
|
||||||
|
);
|
||||||
|
|
||||||
|
const target =
|
||||||
|
typeof targetOrEntityOrEntities === 'function' ||
|
||||||
|
typeof targetOrEntityOrEntities === 'string'
|
||||||
|
? (targetOrEntityOrEntities as EntityTarget<Entity>)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const options = target
|
||||||
|
? (maybeOptionsOrMaybePermissionOptions as SaveOptions | undefined)
|
||||||
|
: (entityOrEntitiesOrMaybeOptions as SaveOptions);
|
||||||
|
|
||||||
|
if (target) {
|
||||||
|
if (Array.isArray(entityOrEntitiesOrMaybeOptions)) {
|
||||||
|
return super.recover(
|
||||||
|
target,
|
||||||
|
entityOrEntitiesOrMaybeOptions as T[],
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return super.recover(
|
||||||
|
target,
|
||||||
|
entityOrEntitiesOrMaybeOptions as T,
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Array.isArray(entityOrEntitiesOrMaybeOptions)) {
|
||||||
|
return super.recover(
|
||||||
|
entityOrEntitiesOrMaybeOptions as Entity | Entity[],
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return super.recover(entityOrEntitiesOrMaybeOptions as Entity, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override restore<Entity extends ObjectLiteral>(
|
||||||
|
targetOrEntity: EntityTarget<Entity>,
|
||||||
|
criteria: unknown,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
|
// if user passed empty criteria or empty list of criterias, then throw an error
|
||||||
|
if (
|
||||||
|
criteria === undefined ||
|
||||||
|
criteria === null ||
|
||||||
|
criteria === '' ||
|
||||||
|
(Array.isArray(criteria) && criteria.length === 0)
|
||||||
|
) {
|
||||||
|
return Promise.reject(
|
||||||
|
new TypeORMError(
|
||||||
|
`Empty criteria(s) are not allowed for the restore method.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
typeof criteria === 'string' ||
|
||||||
|
typeof criteria === 'number' ||
|
||||||
|
criteria instanceof Date ||
|
||||||
|
Array.isArray(criteria)
|
||||||
|
) {
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.restore()
|
||||||
|
.from(targetOrEntity)
|
||||||
|
.whereInIds(criteria)
|
||||||
|
.execute();
|
||||||
|
} else {
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.restore()
|
||||||
|
.from(targetOrEntity)
|
||||||
|
.where(criteria)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override exists<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
options?: FindManyOptions<Entity>,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const metadata = this.connection.getMetadata(entityClass);
|
||||||
|
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
entityClass,
|
||||||
|
FindOptionsUtils.extractFindManyOptionsAlias(options) || metadata.name,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.setFindOptions(options || {})
|
||||||
|
.getExists();
|
||||||
|
}
|
||||||
|
|
||||||
|
override existsBy<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const metadata = this.connection.getMetadata(entityClass);
|
||||||
|
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
entityClass,
|
||||||
|
metadata.name,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.setFindOptions({ where })
|
||||||
|
.getExists();
|
||||||
|
}
|
||||||
|
|
||||||
|
override count<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
options?: FindManyOptions<Entity>,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<number> {
|
||||||
|
const metadata = this.connection.getMetadata(entityClass);
|
||||||
|
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
entityClass,
|
||||||
|
FindOptionsUtils.extractFindManyOptionsAlias(options) || metadata.name,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.setFindOptions(options || {})
|
||||||
|
.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
override countBy<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<number> {
|
||||||
|
const metadata = this.connection.getMetadata(entityClass);
|
||||||
|
|
||||||
|
return this.createQueryBuilder(
|
||||||
|
entityClass,
|
||||||
|
metadata.name,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.setFindOptions({ where })
|
||||||
|
.getCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
async callAggregateFunCustom(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
fnName: string,
|
||||||
|
columnName: string,
|
||||||
|
where = {},
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
) {
|
||||||
|
const metadata = this.connection.getMetadata(entityClass);
|
||||||
|
const column = metadata.columns.find(
|
||||||
|
(item) => item.propertyPath === columnName,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!column) {
|
||||||
|
throw new TypeORMError(
|
||||||
|
`Column "${columnName}" was not found in table "${metadata.name}"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const result = await this.createQueryBuilder(
|
||||||
|
entityClass,
|
||||||
|
metadata.name,
|
||||||
|
this.queryRunner,
|
||||||
|
permissionOptions,
|
||||||
|
)
|
||||||
|
.setFindOptions({ where })
|
||||||
|
.select(
|
||||||
|
`${fnName}(${this.connection.driver.escape(column.databaseName)})`,
|
||||||
|
fnName,
|
||||||
|
)
|
||||||
|
.getRawOne();
|
||||||
|
|
||||||
|
return result[fnName] === null ? null : parseFloat(result[fnName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
override sum<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
columnName: PickKeysByType<Entity, number>,
|
||||||
|
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<number | null> {
|
||||||
|
return this.callAggregateFunCustom(
|
||||||
|
entityClass,
|
||||||
|
'SUM',
|
||||||
|
columnName,
|
||||||
|
where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override average<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
columnName: PickKeysByType<Entity, number>,
|
||||||
|
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<number | null> {
|
||||||
|
return this.callAggregateFunCustom(
|
||||||
|
entityClass,
|
||||||
|
'AVG',
|
||||||
|
columnName,
|
||||||
|
where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override minimum<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
columnName: PickKeysByType<Entity, number>,
|
||||||
|
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<number | null> {
|
||||||
|
return this.callAggregateFunCustom(
|
||||||
|
entityClass,
|
||||||
|
'MIN',
|
||||||
|
columnName,
|
||||||
|
where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override maximum<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
columnName: PickKeysByType<Entity, number>,
|
||||||
|
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<number | null> {
|
||||||
|
return this.callAggregateFunCustom(
|
||||||
|
entityClass,
|
||||||
|
'MAX',
|
||||||
|
columnName,
|
||||||
|
where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override clear<Entity>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
this.validatePermissions(entityClass, 'delete', permissionOptions);
|
||||||
|
|
||||||
|
return super.clear(entityClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
override preload<Entity extends ObjectLiteral>(
|
||||||
|
entityClass: EntityTarget<Entity>,
|
||||||
|
entityLike: DeepPartial<Entity>,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<Entity | undefined> {
|
||||||
|
this.validatePermissions(entityClass, 'select', permissionOptions);
|
||||||
|
|
||||||
|
return super.preload(entityClass, entityLike);
|
||||||
|
}
|
||||||
|
|
||||||
|
override decrement<Entity extends ObjectLiteral>(
|
||||||
|
target: EntityTarget<Entity>,
|
||||||
|
criteria: unknown,
|
||||||
|
propertyPath: string,
|
||||||
|
value: number | string,
|
||||||
|
permissionOptions?: PermissionOptions,
|
||||||
|
): Promise<UpdateResult> {
|
||||||
|
this.validatePermissions(target, 'update', permissionOptions);
|
||||||
|
|
||||||
|
return super.decrement(target, criteria, propertyPath, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,425 @@
|
|||||||
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
|
import {
|
||||||
|
DeepPartial,
|
||||||
|
FindManyOptions,
|
||||||
|
FindOneOptions,
|
||||||
|
FindOptionsWhere,
|
||||||
|
ObjectLiteral,
|
||||||
|
QueryRunner,
|
||||||
|
} 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 { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
|
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||||
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
|
|
||||||
|
describe('WorkspaceRepository', () => {
|
||||||
|
let repository: WorkspaceRepository<ObjectLiteral>;
|
||||||
|
let mockEntityManager: jest.Mocked<WorkspaceEntityManager>;
|
||||||
|
let mockInternalContext: WorkspaceInternalContext;
|
||||||
|
let mockFeatureFlagMap: FeatureFlagMap;
|
||||||
|
let mockObjectRecordsPermissions: ObjectRecordsPermissions;
|
||||||
|
let mockQueryRunner: QueryRunner;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockEntityManager = {
|
||||||
|
find: jest.fn(),
|
||||||
|
findBy: jest.fn(),
|
||||||
|
findAndCount: jest.fn(),
|
||||||
|
findAndCountBy: jest.fn(),
|
||||||
|
findOne: jest.fn(),
|
||||||
|
findOneBy: jest.fn(),
|
||||||
|
findOneOrFail: jest.fn(),
|
||||||
|
findOneByOrFail: jest.fn(),
|
||||||
|
save: jest.fn(),
|
||||||
|
remove: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
softRemove: jest.fn(),
|
||||||
|
softDelete: jest.fn(),
|
||||||
|
recover: jest.fn(),
|
||||||
|
restore: jest.fn(),
|
||||||
|
insert: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
upsert: jest.fn(),
|
||||||
|
exists: jest.fn(),
|
||||||
|
existsBy: jest.fn(),
|
||||||
|
count: jest.fn(),
|
||||||
|
countBy: jest.fn(),
|
||||||
|
sum: jest.fn(),
|
||||||
|
average: jest.fn(),
|
||||||
|
minimum: jest.fn(),
|
||||||
|
maximum: jest.fn(),
|
||||||
|
increment: jest.fn(),
|
||||||
|
decrement: jest.fn(),
|
||||||
|
preload: jest.fn(),
|
||||||
|
clear: jest.fn(),
|
||||||
|
} as unknown as jest.Mocked<WorkspaceEntityManager>;
|
||||||
|
|
||||||
|
mockInternalContext = {
|
||||||
|
workspaceId: 'test-workspace-id',
|
||||||
|
objectMetadataMaps: {
|
||||||
|
idByNameSingular: {},
|
||||||
|
},
|
||||||
|
featureFlagsMap: {},
|
||||||
|
} as WorkspaceInternalContext;
|
||||||
|
|
||||||
|
mockFeatureFlagMap = Object.values(FeatureFlagKey).reduce(
|
||||||
|
(acc, key) => ({ ...acc, [key]: false }),
|
||||||
|
{} as FeatureFlagMap,
|
||||||
|
);
|
||||||
|
mockObjectRecordsPermissions = {
|
||||||
|
'test-entity': {
|
||||||
|
canRead: true,
|
||||||
|
canUpdate: false,
|
||||||
|
canSoftDelete: false,
|
||||||
|
canDestroy: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockQueryRunner = {} as QueryRunner;
|
||||||
|
|
||||||
|
repository = new WorkspaceRepository(
|
||||||
|
mockInternalContext,
|
||||||
|
'test-entity',
|
||||||
|
mockEntityManager,
|
||||||
|
mockFeatureFlagMap,
|
||||||
|
mockQueryRunner,
|
||||||
|
mockObjectRecordsPermissions,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Mock the private methods
|
||||||
|
jest
|
||||||
|
.spyOn(repository as any, 'getObjectMetadataFromTarget')
|
||||||
|
.mockResolvedValue({
|
||||||
|
id: 'test-metadata-id',
|
||||||
|
nameSingular: 'test-entity',
|
||||||
|
namePlural: 'test-entities',
|
||||||
|
fields: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
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', () => {
|
||||||
|
it('should delegate to workspaceEntityManager find', async () => {
|
||||||
|
const options: FindManyOptions<ObjectLiteral> = {
|
||||||
|
where: { id: 'test-id' },
|
||||||
|
};
|
||||||
|
|
||||||
|
mockEntityManager.find.mockResolvedValue([{ id: 'test-id' }]);
|
||||||
|
|
||||||
|
await repository.find(options);
|
||||||
|
|
||||||
|
expect(mockEntityManager.find).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ where: { id: 'test-id' } },
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delegate to workspaceEntityManager findBy', async () => {
|
||||||
|
const where: FindOptionsWhere<ObjectLiteral> = { id: 'test-id' };
|
||||||
|
|
||||||
|
mockEntityManager.findBy.mockResolvedValue([{ id: 'test-id' }]);
|
||||||
|
|
||||||
|
await repository.findBy(where);
|
||||||
|
|
||||||
|
expect(mockEntityManager.findBy).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ id: 'test-id' },
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delegate to workspaceEntityManager findAndCount', async () => {
|
||||||
|
const options: FindManyOptions<ObjectLiteral> = {
|
||||||
|
where: { id: 'test-id' },
|
||||||
|
};
|
||||||
|
|
||||||
|
mockEntityManager.findAndCount.mockResolvedValue([
|
||||||
|
[{ id: 'test-id' }],
|
||||||
|
1,
|
||||||
|
]);
|
||||||
|
|
||||||
|
await repository.findAndCount(options);
|
||||||
|
|
||||||
|
expect(mockEntityManager.findAndCount).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ where: { id: 'test-id' } },
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delegate to workspaceEntityManager findOne', async () => {
|
||||||
|
const options: FindOneOptions<ObjectLiteral> = {
|
||||||
|
where: { id: 'test-id' },
|
||||||
|
};
|
||||||
|
|
||||||
|
mockEntityManager.findOne.mockResolvedValue({ id: 'test-id' });
|
||||||
|
|
||||||
|
await repository.findOne(options);
|
||||||
|
|
||||||
|
expect(mockEntityManager.findOne).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ where: { id: 'test-id' } },
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Save Methods', () => {
|
||||||
|
it('should delegate to workspaceEntityManager save', async () => {
|
||||||
|
const entity: DeepPartial<ObjectLiteral> = { id: 'test-id' };
|
||||||
|
|
||||||
|
mockEntityManager.save.mockResolvedValue({ id: 'test-id' });
|
||||||
|
|
||||||
|
await repository.save(entity);
|
||||||
|
|
||||||
|
expect(mockEntityManager.save).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ id: 'test-id' },
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Remove Methods', () => {
|
||||||
|
it('should delegate to workspaceEntityManager remove', async () => {
|
||||||
|
const entity: ObjectLiteral = { id: 'test-id' };
|
||||||
|
const expectedResult = [{ id: 'test-id' }];
|
||||||
|
|
||||||
|
mockEntityManager.remove.mockResolvedValue(expectedResult);
|
||||||
|
|
||||||
|
const result = await repository.remove(entity);
|
||||||
|
|
||||||
|
expect(mockEntityManager.remove).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ id: 'test-id' },
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delegate to workspaceEntityManager delete', async () => {
|
||||||
|
const criteria: FindOptionsWhere<ObjectLiteral> = { id: 'test-id' };
|
||||||
|
const expectedResult = { affected: 1, raw: [] };
|
||||||
|
|
||||||
|
mockEntityManager.delete.mockResolvedValue(expectedResult);
|
||||||
|
|
||||||
|
const result = await repository.delete(criteria);
|
||||||
|
|
||||||
|
expect(mockEntityManager.delete).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ id: 'test-id' },
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(result).toEqual(expectedResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Insert Methods', () => {
|
||||||
|
it('should delegate to workspaceEntityManager insert', async () => {
|
||||||
|
const entity: DeepPartial<ObjectLiteral> = { id: 'test-id' };
|
||||||
|
|
||||||
|
mockEntityManager.insert.mockResolvedValue({
|
||||||
|
identifiers: [{ id: 'test-id' }],
|
||||||
|
generatedMaps: [{ id: 'test-id' }],
|
||||||
|
raw: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await repository.insert(entity);
|
||||||
|
|
||||||
|
expect(mockEntityManager.insert).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ id: 'test-id' },
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delegate to workspaceEntityManager upsert', async () => {
|
||||||
|
const entity: DeepPartial<ObjectLiteral> = { id: 'test-id' };
|
||||||
|
|
||||||
|
mockEntityManager.upsert.mockResolvedValue({
|
||||||
|
identifiers: [{ id: 'test-id' }],
|
||||||
|
generatedMaps: [{ id: 'test-id' }],
|
||||||
|
raw: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await repository.upsert(entity, ['id']);
|
||||||
|
|
||||||
|
expect(mockEntityManager.upsert).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ id: 'test-id' },
|
||||||
|
['id'],
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Update Methods', () => {
|
||||||
|
it('should delegate to workspaceEntityManager update', async () => {
|
||||||
|
const criteria: FindOptionsWhere<ObjectLiteral> = { id: 'test-id' };
|
||||||
|
const partialEntity: DeepPartial<ObjectLiteral> = { name: 'test' };
|
||||||
|
|
||||||
|
mockEntityManager.update.mockResolvedValue({
|
||||||
|
affected: 1,
|
||||||
|
raw: [],
|
||||||
|
generatedMaps: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await repository.update(criteria, partialEntity);
|
||||||
|
|
||||||
|
expect(mockEntityManager.update).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ id: 'test-id' },
|
||||||
|
{ name: 'test' },
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Math Methods', () => {
|
||||||
|
it('should delegate to workspaceEntityManager sum', async () => {
|
||||||
|
const where: FindOptionsWhere<ObjectLiteral> = { id: 'test-id' };
|
||||||
|
|
||||||
|
mockEntityManager.sum.mockResolvedValue(100);
|
||||||
|
|
||||||
|
await repository.sum('testColumn', where);
|
||||||
|
|
||||||
|
expect(mockEntityManager.sum).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
'testColumn',
|
||||||
|
{ id: 'test-id' },
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delegate to workspaceEntityManager increment', async () => {
|
||||||
|
const conditions: FindOptionsWhere<ObjectLiteral> = { id: 'test-id' };
|
||||||
|
|
||||||
|
mockEntityManager.increment.mockResolvedValue({
|
||||||
|
affected: 1,
|
||||||
|
raw: [],
|
||||||
|
generatedMaps: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
await repository.increment(conditions, 'testColumn', 1);
|
||||||
|
|
||||||
|
expect(mockEntityManager.increment).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ id: 'test-id' },
|
||||||
|
'testColumn',
|
||||||
|
1,
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Preload and Clear Methods', () => {
|
||||||
|
it('should delegate to workspaceEntityManager preload', async () => {
|
||||||
|
const entityLike: DeepPartial<ObjectLiteral> = { id: 'test-id' };
|
||||||
|
|
||||||
|
mockEntityManager.preload.mockResolvedValue({ id: 'test-id' });
|
||||||
|
|
||||||
|
await repository.preload(entityLike);
|
||||||
|
|
||||||
|
expect(mockEntityManager.preload).toHaveBeenCalledWith(
|
||||||
|
'test-entity',
|
||||||
|
{ id: 'test-id' },
|
||||||
|
{
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delegate to workspaceEntityManager clear', async () => {
|
||||||
|
mockEntityManager.clear.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await repository.clear();
|
||||||
|
|
||||||
|
expect(mockEntityManager.clear).toHaveBeenCalledWith('test-entity', {
|
||||||
|
shouldBypassPermissionChecks: false,
|
||||||
|
objectRecordsPermissions: mockObjectRecordsPermissions,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Restricted Methods', () => {
|
||||||
|
it('should throw error for query', async () => {
|
||||||
|
await expect(repository.query()).rejects.toThrow('Method not allowed.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for findByIds', async () => {
|
||||||
|
await expect(repository.findByIds()).rejects.toThrow(
|
||||||
|
'findByIds is deprecated. Please use findBy with In operator instead.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for findOneById', async () => {
|
||||||
|
await expect(repository.findOneById()).rejects.toThrow(
|
||||||
|
'findOneById is deprecated. Please use findOneBy with id condition instead.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw error for exist', async () => {
|
||||||
|
await expect(repository.exist()).rejects.toThrow(
|
||||||
|
'exist is deprecated. Please use exists method instead.',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -94,7 +94,15 @@ export class WorkspaceRepository<
|
|||||||
): Promise<T[]> {
|
): Promise<T[]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions(options);
|
const computedOptions = await this.transformOptions(options);
|
||||||
const result = await manager.find(this.target, computedOptions);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
const result = await manager.find(
|
||||||
|
this.target,
|
||||||
|
computedOptions,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
const formattedResult = await this.formatResult(result);
|
||||||
|
|
||||||
return formattedResult;
|
return formattedResult;
|
||||||
@ -106,7 +114,15 @@ export class WorkspaceRepository<
|
|||||||
): Promise<T[]> {
|
): Promise<T[]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions({ where });
|
const computedOptions = await this.transformOptions({ where });
|
||||||
const result = await manager.findBy(this.target, computedOptions.where);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
const result = await manager.findBy(
|
||||||
|
this.target,
|
||||||
|
computedOptions.where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
const formattedResult = await this.formatResult(result);
|
||||||
|
|
||||||
return formattedResult;
|
return formattedResult;
|
||||||
@ -118,7 +134,15 @@ export class WorkspaceRepository<
|
|||||||
): Promise<[T[], number]> {
|
): Promise<[T[], number]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions(options);
|
const computedOptions = await this.transformOptions(options);
|
||||||
const result = await manager.findAndCount(this.target, computedOptions);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
const result = await manager.findAndCount(
|
||||||
|
this.target,
|
||||||
|
computedOptions,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
const formattedResult = await this.formatResult(result);
|
||||||
|
|
||||||
return formattedResult;
|
return formattedResult;
|
||||||
@ -130,9 +154,14 @@ export class WorkspaceRepository<
|
|||||||
): Promise<[T[], number]> {
|
): Promise<[T[], number]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions({ where });
|
const computedOptions = await this.transformOptions({ where });
|
||||||
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
const result = await manager.findAndCountBy(
|
const result = await manager.findAndCountBy(
|
||||||
this.target,
|
this.target,
|
||||||
computedOptions.where,
|
computedOptions.where,
|
||||||
|
permissionOptions,
|
||||||
);
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
const formattedResult = await this.formatResult(result);
|
||||||
|
|
||||||
@ -145,7 +174,15 @@ export class WorkspaceRepository<
|
|||||||
): Promise<T | null> {
|
): Promise<T | null> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions(options);
|
const computedOptions = await this.transformOptions(options);
|
||||||
const result = await manager.findOne(this.target, computedOptions);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
const result = await manager.findOne(
|
||||||
|
this.target,
|
||||||
|
computedOptions,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
const formattedResult = await this.formatResult(result);
|
||||||
|
|
||||||
return formattedResult;
|
return formattedResult;
|
||||||
@ -157,7 +194,15 @@ export class WorkspaceRepository<
|
|||||||
): Promise<T | null> {
|
): Promise<T | null> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions({ where });
|
const computedOptions = await this.transformOptions({ where });
|
||||||
const result = await manager.findOneBy(this.target, computedOptions.where);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
const result = await manager.findOneBy(
|
||||||
|
this.target,
|
||||||
|
computedOptions.where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
const formattedResult = await this.formatResult(result);
|
||||||
|
|
||||||
return formattedResult;
|
return formattedResult;
|
||||||
@ -169,7 +214,15 @@ export class WorkspaceRepository<
|
|||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions(options);
|
const computedOptions = await this.transformOptions(options);
|
||||||
const result = await manager.findOneOrFail(this.target, computedOptions);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
const result = await manager.findOneOrFail(
|
||||||
|
this.target,
|
||||||
|
computedOptions,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
const formattedResult = await this.formatResult(result);
|
||||||
|
|
||||||
return formattedResult;
|
return formattedResult;
|
||||||
@ -181,9 +234,14 @@ export class WorkspaceRepository<
|
|||||||
): Promise<T> {
|
): Promise<T> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions({ where });
|
const computedOptions = await this.transformOptions({ where });
|
||||||
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
const result = await manager.findOneByOrFail(
|
const result = await manager.findOneByOrFail(
|
||||||
this.target,
|
this.target,
|
||||||
computedOptions.where,
|
computedOptions.where,
|
||||||
|
permissionOptions,
|
||||||
);
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
const formattedResult = await this.formatResult(result);
|
||||||
|
|
||||||
@ -219,25 +277,32 @@ export class WorkspaceRepository<
|
|||||||
|
|
||||||
override async save<U extends DeepPartial<T>>(
|
override async save<U extends DeepPartial<T>>(
|
||||||
entityOrEntities: U | U[],
|
entityOrEntities: U | U[],
|
||||||
options?: SaveOptions,
|
options?: SaveOptions | (SaveOptions & { reload: false }),
|
||||||
entityManager?: WorkspaceEntityManager,
|
entityManager?: WorkspaceEntityManager,
|
||||||
): Promise<U | U[]> {
|
): Promise<U | U[]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||||
let result: U | U[];
|
let result: U | U[];
|
||||||
|
|
||||||
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
||||||
if (Array.isArray(formattedEntityOrEntities)) {
|
if (Array.isArray(formattedEntityOrEntities)) {
|
||||||
result = await manager.save(
|
result = await manager.save(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
formattedEntityOrEntities,
|
||||||
options,
|
options,
|
||||||
|
permissionOptions,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result = await manager.save(
|
result = await manager.save(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
formattedEntityOrEntities,
|
||||||
options,
|
options,
|
||||||
|
permissionOptions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,10 +333,15 @@ export class WorkspaceRepository<
|
|||||||
): Promise<T | T[]> {
|
): Promise<T | T[]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||||
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
const result = await manager.remove(
|
const result = await manager.remove(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
formattedEntityOrEntities,
|
||||||
options,
|
options,
|
||||||
|
permissionOptions,
|
||||||
);
|
);
|
||||||
|
|
||||||
const formattedResult = await this.formatResult(result);
|
const formattedResult = await this.formatResult(result);
|
||||||
@ -298,7 +368,12 @@ export class WorkspaceRepository<
|
|||||||
criteria = await this.transformOptions(criteria);
|
criteria = await this.transformOptions(criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager.delete(this.target, criteria);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.delete(this.target, criteria, permissionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
override softRemove<U extends DeepPartial<T>>(
|
override softRemove<U extends DeepPartial<T>>(
|
||||||
@ -332,20 +407,26 @@ export class WorkspaceRepository<
|
|||||||
): Promise<U | U[]> {
|
): Promise<U | U[]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||||
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
let result: U | U[];
|
let result: U | U[];
|
||||||
|
|
||||||
// Needed becasuse save method has multiple signature, otherwise we will need to do a type assertion
|
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
||||||
if (Array.isArray(formattedEntityOrEntities)) {
|
if (Array.isArray(formattedEntityOrEntities)) {
|
||||||
result = await manager.softRemove(
|
result = await manager.softRemove(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
formattedEntityOrEntities,
|
||||||
options,
|
options,
|
||||||
|
permissionOptions,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result = await manager.softRemove(
|
result = await manager.softRemove(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
formattedEntityOrEntities,
|
||||||
options,
|
options,
|
||||||
|
permissionOptions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -373,7 +454,12 @@ export class WorkspaceRepository<
|
|||||||
criteria = await this.transformOptions(criteria);
|
criteria = await this.transformOptions(criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager.softDelete(this.target, criteria);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.softDelete(this.target, criteria, permissionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -410,20 +496,26 @@ export class WorkspaceRepository<
|
|||||||
): Promise<U | U[]> {
|
): Promise<U | U[]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||||
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
let result: U | U[];
|
let result: U | U[];
|
||||||
|
|
||||||
// Needed becasuse save method has multiple signature, otherwise we will need to do a type assertion
|
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
||||||
if (Array.isArray(formattedEntityOrEntities)) {
|
if (Array.isArray(formattedEntityOrEntities)) {
|
||||||
result = await manager.recover(
|
result = await manager.recover(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
formattedEntityOrEntities,
|
||||||
options,
|
options,
|
||||||
|
permissionOptions,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result = await manager.recover(
|
result = await manager.recover(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
formattedEntityOrEntities,
|
||||||
options,
|
options,
|
||||||
|
permissionOptions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -451,7 +543,12 @@ export class WorkspaceRepository<
|
|||||||
criteria = await this.transformOptions(criteria);
|
criteria = await this.transformOptions(criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager.restore(this.target, criteria);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.restore(this.target, criteria, permissionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -464,10 +561,15 @@ export class WorkspaceRepository<
|
|||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
|
|
||||||
const formattedEntity = await this.formatData(entity);
|
const formattedEntity = await this.formatData(entity);
|
||||||
const result = await manager.insert(this.target, formattedEntity, {
|
const permissionOptions = {
|
||||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
});
|
};
|
||||||
|
const result = await manager.insert(
|
||||||
|
this.target,
|
||||||
|
formattedEntity,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
const formattedResult = await this.formatResult(result.generatedMaps);
|
const formattedResult = await this.formatResult(result.generatedMaps);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -500,7 +602,17 @@ export class WorkspaceRepository<
|
|||||||
criteria = await this.transformOptions(criteria);
|
criteria = await this.transformOptions(criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager.update(this.target, criteria, partialEntity);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.update(
|
||||||
|
this.target,
|
||||||
|
criteria,
|
||||||
|
partialEntity,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async upsert(
|
override async upsert(
|
||||||
@ -512,14 +624,16 @@ export class WorkspaceRepository<
|
|||||||
|
|
||||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
||||||
|
|
||||||
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
const result = await manager.upsert(
|
const result = await manager.upsert(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
formattedEntityOrEntities,
|
||||||
conflictPathsOrOptions,
|
conflictPathsOrOptions,
|
||||||
{
|
permissionOptions,
|
||||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
|
||||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const formattedResult = await this.formatResult(result.generatedMaps);
|
const formattedResult = await this.formatResult(result.generatedMaps);
|
||||||
@ -541,7 +655,12 @@ export class WorkspaceRepository<
|
|||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions(options);
|
const computedOptions = await this.transformOptions(options);
|
||||||
|
|
||||||
return manager.exists(this.target, computedOptions);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.exists(this.target, computedOptions, permissionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async existsBy(
|
override async existsBy(
|
||||||
@ -551,7 +670,16 @@ export class WorkspaceRepository<
|
|||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions({ where });
|
const computedOptions = await this.transformOptions({ where });
|
||||||
|
|
||||||
return manager.existsBy(this.target, computedOptions.where);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.existsBy(
|
||||||
|
this.target,
|
||||||
|
computedOptions.where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -564,7 +692,12 @@ export class WorkspaceRepository<
|
|||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions(options);
|
const computedOptions = await this.transformOptions(options);
|
||||||
|
|
||||||
return manager.count(this.target, computedOptions);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.count(this.target, computedOptions, permissionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async countBy(
|
override async countBy(
|
||||||
@ -574,7 +707,16 @@ export class WorkspaceRepository<
|
|||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions({ where });
|
const computedOptions = await this.transformOptions({ where });
|
||||||
|
|
||||||
return manager.countBy(this.target, computedOptions.where);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.countBy(
|
||||||
|
this.target,
|
||||||
|
computedOptions.where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -588,7 +730,17 @@ export class WorkspaceRepository<
|
|||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions({ where });
|
const computedOptions = await this.transformOptions({ where });
|
||||||
|
|
||||||
return manager.sum(this.target, columnName, computedOptions.where);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.sum(
|
||||||
|
this.target,
|
||||||
|
columnName,
|
||||||
|
computedOptions.where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async average(
|
override async average(
|
||||||
@ -599,7 +751,17 @@ export class WorkspaceRepository<
|
|||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions({ where });
|
const computedOptions = await this.transformOptions({ where });
|
||||||
|
|
||||||
return manager.average(this.target, columnName, computedOptions.where);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.average(
|
||||||
|
this.target,
|
||||||
|
columnName,
|
||||||
|
computedOptions.where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async minimum(
|
override async minimum(
|
||||||
@ -610,7 +772,17 @@ export class WorkspaceRepository<
|
|||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions({ where });
|
const computedOptions = await this.transformOptions({ where });
|
||||||
|
|
||||||
return manager.minimum(this.target, columnName, computedOptions.where);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.minimum(
|
||||||
|
this.target,
|
||||||
|
columnName,
|
||||||
|
computedOptions.where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async maximum(
|
override async maximum(
|
||||||
@ -621,7 +793,17 @@ export class WorkspaceRepository<
|
|||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const computedOptions = await this.transformOptions({ where });
|
const computedOptions = await this.transformOptions({ where });
|
||||||
|
|
||||||
return manager.maximum(this.target, columnName, computedOptions.where);
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.maximum(
|
||||||
|
this.target,
|
||||||
|
columnName,
|
||||||
|
computedOptions.where,
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override async increment(
|
override async increment(
|
||||||
@ -635,11 +817,17 @@ export class WorkspaceRepository<
|
|||||||
where: conditions,
|
where: conditions,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
return manager.increment(
|
return manager.increment(
|
||||||
this.target,
|
this.target,
|
||||||
computedConditions.where,
|
computedConditions.where,
|
||||||
propertyPath,
|
propertyPath,
|
||||||
value,
|
value,
|
||||||
|
permissionOptions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,14 +842,73 @@ export class WorkspaceRepository<
|
|||||||
where: conditions,
|
where: conditions,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
return manager.decrement(
|
return manager.decrement(
|
||||||
this.target,
|
this.target,
|
||||||
computedConditions.where,
|
computedConditions.where,
|
||||||
propertyPath,
|
propertyPath,
|
||||||
value,
|
value,
|
||||||
|
permissionOptions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PRELOAD METHOD
|
||||||
|
*/
|
||||||
|
override async preload<U extends DeepPartial<T>>(
|
||||||
|
entityLike: U,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CLEAR METHOD
|
||||||
|
*/
|
||||||
|
override async clear(entityManager?: WorkspaceEntityManager): Promise<void> {
|
||||||
|
const manager = entityManager || this.manager;
|
||||||
|
const permissionOptions = {
|
||||||
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
|
};
|
||||||
|
|
||||||
|
return manager.clear(this.target, permissionOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DEPRECATED AND RESTRICTED METHODS
|
||||||
|
*/
|
||||||
|
override async query(): Promise<unknown> {
|
||||||
|
throw new Error('Method not allowed.');
|
||||||
|
}
|
||||||
|
|
||||||
|
override async findByIds(): Promise<T[]> {
|
||||||
|
throw new Error(
|
||||||
|
'findByIds is deprecated. Please use findBy with In operator instead.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async findOneById(): Promise<T | null> {
|
||||||
|
throw new Error(
|
||||||
|
'findOneById is deprecated. Please use findOneBy with id condition instead.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async exist(): Promise<boolean> {
|
||||||
|
throw new Error('exist is deprecated. Please use exists method instead.');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PRIVATE METHODS
|
* PRIVATE METHODS
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user