[permissions] Implement object-records permissions in query builders (#11458)
In this PR we are - (if permissionsV2 is enabled) executing permission checks at query builder level. To do so we want to override the query builders methods that are performing db calls (.execute(), .getMany(), ... etc.) For now I have just overriden some of the query builders methods for the poc. To do so I created custom query builder classes that extend typeorm's query builder (selectQueryBuilder and updateQueryBuilder, for now and later I will tackle softDeleteQueryBuilder, etc.). - adding a notion of roles permissions version and roles permissions object to datasources. We will now use one datasource per roleId and rolePermissionVersion. Both rolesPermissionsVersion and rolesPermissions objects are stored in redis and recomputed at role update or if queried and found empty. Unlike for metadata version we don't need to store a version in the db that stands for the source of truth. We also don't need to destroy and recreate the datasource if the rolesPermissions version changes, but only to update the value for rolesPermissions and rolesPermissionsVersions on the existing datasource. What this PR misses - computing of roles permissions should take into account objectPermissions table (for now it only looks at what's on the roles table) - pursue extension of query builder classes and overriding of their db calling-methods - what should the behaviour be for calls from twentyOrmGlobalManager that don't have a roleId?
This commit is contained in:
@ -10,6 +10,7 @@ import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-r
|
|||||||
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
|
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
|
||||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||||
|
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
|
||||||
describe('GraphqlQueryFindDuplicatesResolverService', () => {
|
describe('GraphqlQueryFindDuplicatesResolverService', () => {
|
||||||
@ -27,6 +28,7 @@ describe('GraphqlQueryFindDuplicatesResolverService', () => {
|
|||||||
ProcessNestedRelationsHelper,
|
ProcessNestedRelationsHelper,
|
||||||
FeatureFlagService,
|
FeatureFlagService,
|
||||||
PermissionsService,
|
PermissionsService,
|
||||||
|
UserRoleService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
.overrideProvider(WorkspaceQueryHookService)
|
.overrideProvider(WorkspaceQueryHookService)
|
||||||
@ -45,6 +47,8 @@ describe('GraphqlQueryFindDuplicatesResolverService', () => {
|
|||||||
.useValue({})
|
.useValue({})
|
||||||
.overrideProvider(PermissionsService)
|
.overrideProvider(PermissionsService)
|
||||||
.useValue({})
|
.useValue({})
|
||||||
|
.overrideProvider(UserRoleService)
|
||||||
|
.useValue({})
|
||||||
.compile();
|
.compile();
|
||||||
|
|
||||||
service = module.get<GraphqlQueryFindDuplicatesResolverService>(
|
service = module.get<GraphqlQueryFindDuplicatesResolverService>(
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
|
import { ProcessAggregateHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-aggregate.helper';
|
||||||
import { ProcessNestedRelationsV2Helper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper';
|
import { ProcessNestedRelationsV2Helper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations-v2.helper';
|
||||||
@ -19,8 +20,9 @@ import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/gra
|
|||||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
||||||
import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module';
|
import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module';
|
||||||
import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module';
|
import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module';
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
|
||||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||||
|
|
||||||
const graphqlQueryResolvers = [
|
const graphqlQueryResolvers = [
|
||||||
GraphqlQueryCreateManyResolverService,
|
GraphqlQueryCreateManyResolverService,
|
||||||
@ -42,8 +44,9 @@ const graphqlQueryResolvers = [
|
|||||||
imports: [
|
imports: [
|
||||||
WorkspaceQueryHookModule,
|
WorkspaceQueryHookModule,
|
||||||
WorkspaceQueryRunnerModule,
|
WorkspaceQueryRunnerModule,
|
||||||
FeatureFlagModule,
|
|
||||||
PermissionsModule,
|
PermissionsModule,
|
||||||
|
TypeOrmModule.forFeature([UserWorkspaceRoleEntity], 'metadata'),
|
||||||
|
UserRoleModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ApiEventEmitterService,
|
ApiEventEmitterService,
|
||||||
|
|||||||
@ -1,12 +1,11 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
import {
|
import {
|
||||||
DataSource,
|
|
||||||
FindOptionsRelations,
|
FindOptionsRelations,
|
||||||
ObjectLiteral,
|
ObjectLiteral,
|
||||||
SelectQueryBuilder,
|
SelectQueryBuilder,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
|
||||||
|
|
||||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||||
@ -22,6 +21,7 @@ import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.typ
|
|||||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||||
|
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
import { isFieldMetadataOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
|
||||||
@ -41,6 +41,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
limit,
|
limit,
|
||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||||
@ -50,7 +51,8 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
aggregate?: Record<string, AggregationField>;
|
aggregate?: Record<string, AggregationField>;
|
||||||
limit: number;
|
limit: number;
|
||||||
authContext: AuthContext;
|
authContext: AuthContext;
|
||||||
dataSource: DataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const processRelationTasks = Object.entries(relations).map(
|
const processRelationTasks = Object.entries(relations).map(
|
||||||
([sourceFieldName, nestedRelations]) =>
|
([sourceFieldName, nestedRelations]) =>
|
||||||
@ -65,6 +67,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
limit,
|
limit,
|
||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
roleId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -82,6 +85,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
limit,
|
limit,
|
||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||||
@ -92,7 +96,8 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
aggregate: Record<string, AggregationField>;
|
aggregate: Record<string, AggregationField>;
|
||||||
limit: number;
|
limit: number;
|
||||||
authContext: AuthContext;
|
authContext: AuthContext;
|
||||||
dataSource: DataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const sourceFieldMetadata =
|
const sourceFieldMetadata =
|
||||||
parentObjectMetadataItem.fieldsByName[sourceFieldName];
|
parentObjectMetadataItem.fieldsByName[sourceFieldName];
|
||||||
@ -121,7 +126,9 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
|
|
||||||
const targetObjectRepository = dataSource.getRepository(
|
const targetObjectRepository = dataSource.getRepository(
|
||||||
targetObjectMetadata.nameSingular,
|
targetObjectMetadata.nameSingular,
|
||||||
|
roleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetObjectQueryBuilder = targetObjectRepository.createQueryBuilder(
|
const targetObjectQueryBuilder = targetObjectRepository.createQueryBuilder(
|
||||||
targetObjectMetadata.nameSingular,
|
targetObjectMetadata.nameSingular,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -24,6 +24,7 @@ import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.typ
|
|||||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||||
|
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
import { deduceRelationDirection } from 'src/engine/utils/deduce-relation-direction.util';
|
import { deduceRelationDirection } from 'src/engine/utils/deduce-relation-direction.util';
|
||||||
|
|
||||||
@ -45,6 +46,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||||
@ -54,8 +56,9 @@ export class ProcessNestedRelationsHelper {
|
|||||||
aggregate?: Record<string, AggregationField>;
|
aggregate?: Record<string, AggregationField>;
|
||||||
limit: number;
|
limit: number;
|
||||||
authContext: AuthContext;
|
authContext: AuthContext;
|
||||||
dataSource: DataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
isNewRelationEnabled: boolean;
|
isNewRelationEnabled: boolean;
|
||||||
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
if (isNewRelationEnabled) {
|
if (isNewRelationEnabled) {
|
||||||
return this.processNestedRelationsV2Helper.processNestedRelations({
|
return this.processNestedRelationsV2Helper.processNestedRelations({
|
||||||
@ -68,6 +71,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
limit,
|
limit,
|
||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,6 +89,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
roleId,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -103,6 +108,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||||
@ -115,6 +121,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext: any;
|
authContext: any;
|
||||||
dataSource: DataSource;
|
dataSource: DataSource;
|
||||||
isNewRelationEnabled: boolean;
|
isNewRelationEnabled: boolean;
|
||||||
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const relationFieldMetadata =
|
const relationFieldMetadata =
|
||||||
parentObjectMetadataItem.fieldsByName[relationName];
|
parentObjectMetadataItem.fieldsByName[relationName];
|
||||||
@ -141,6 +148,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,6 +164,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||||
@ -165,9 +174,10 @@ export class ProcessNestedRelationsHelper {
|
|||||||
nestedRelations: any;
|
nestedRelations: any;
|
||||||
aggregate: Record<string, AggregationField>;
|
aggregate: Record<string, AggregationField>;
|
||||||
limit: number;
|
limit: number;
|
||||||
authContext: any;
|
authContext: AuthContext;
|
||||||
dataSource: DataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
isNewRelationEnabled: boolean;
|
isNewRelationEnabled: boolean;
|
||||||
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { inverseRelationName, referenceObjectMetadata } =
|
const { inverseRelationName, referenceObjectMetadata } =
|
||||||
this.getRelationMetadata({
|
this.getRelationMetadata({
|
||||||
@ -175,8 +185,10 @@ export class ProcessNestedRelationsHelper {
|
|||||||
parentObjectMetadataItem,
|
parentObjectMetadataItem,
|
||||||
relationName,
|
relationName,
|
||||||
});
|
});
|
||||||
|
|
||||||
const relationRepository = dataSource.getRepository(
|
const relationRepository = dataSource.getRepository(
|
||||||
referenceObjectMetadata.nameSingular,
|
referenceObjectMetadata.nameSingular,
|
||||||
|
roleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const referenceQueryBuilder = relationRepository.createQueryBuilder(
|
const referenceQueryBuilder = relationRepository.createQueryBuilder(
|
||||||
@ -252,6 +264,7 @@ export class ProcessNestedRelationsHelper {
|
|||||||
authContext,
|
authContext,
|
||||||
dataSource,
|
dataSource,
|
||||||
isNewRelationEnabled,
|
isNewRelationEnabled,
|
||||||
|
roleId,
|
||||||
}: {
|
}: {
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
parentObjectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||||
@ -262,16 +275,19 @@ export class ProcessNestedRelationsHelper {
|
|||||||
aggregate: Record<string, AggregationField>;
|
aggregate: Record<string, AggregationField>;
|
||||||
limit: number;
|
limit: number;
|
||||||
authContext: any;
|
authContext: any;
|
||||||
dataSource: DataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
isNewRelationEnabled: boolean;
|
isNewRelationEnabled: boolean;
|
||||||
|
roleId?: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const { referenceObjectMetadata } = this.getRelationMetadata({
|
const { referenceObjectMetadata } = this.getRelationMetadata({
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
parentObjectMetadataItem,
|
parentObjectMetadataItem,
|
||||||
relationName,
|
relationName,
|
||||||
});
|
});
|
||||||
|
|
||||||
const relationRepository = dataSource.getRepository(
|
const relationRepository = dataSource.getRepository(
|
||||||
referenceObjectMetadata.nameSingular,
|
referenceObjectMetadata.nameSingular,
|
||||||
|
roleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const referenceQueryBuilder = relationRepository.createQueryBuilder(
|
const referenceQueryBuilder = relationRepository.createQueryBuilder(
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||||||
import graphqlFields from 'graphql-fields';
|
import graphqlFields from 'graphql-fields';
|
||||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||||
import { capitalize, isDefined } from 'twenty-shared/utils';
|
import { capitalize, isDefined } from 'twenty-shared/utils';
|
||||||
import { DataSource, ObjectLiteral } from 'typeorm';
|
import { ObjectLiteral } from 'typeorm';
|
||||||
|
|
||||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
|
||||||
@ -26,7 +26,6 @@ import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/g
|
|||||||
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
|
import { WorkspaceQueryHookService } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.service';
|
||||||
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
|
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
|
||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
|
||||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
import {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
@ -34,16 +33,19 @@ import {
|
|||||||
PermissionsExceptionMessage,
|
PermissionsExceptionMessage,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||||
|
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||||
|
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
|
||||||
export type GraphqlQueryResolverExecutionArgs<Input extends ResolverArgs> = {
|
export type GraphqlQueryResolverExecutionArgs<Input extends ResolverArgs> = {
|
||||||
args: Input;
|
args: Input;
|
||||||
options: WorkspaceQueryRunnerOptions;
|
options: WorkspaceQueryRunnerOptions;
|
||||||
dataSource: DataSource;
|
dataSource: WorkspaceDataSource;
|
||||||
repository: WorkspaceRepository<ObjectLiteral>;
|
repository: WorkspaceRepository<ObjectLiteral>;
|
||||||
graphqlQueryParser: GraphqlQueryParser;
|
graphqlQueryParser: GraphqlQueryParser;
|
||||||
graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult;
|
graphqlQuerySelectedFieldsResult: GraphqlQuerySelectedFieldsResult;
|
||||||
|
roleId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -68,9 +70,9 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
@Inject()
|
@Inject()
|
||||||
protected readonly processNestedRelationsHelper: ProcessNestedRelationsHelper;
|
protected readonly processNestedRelationsHelper: ProcessNestedRelationsHelper;
|
||||||
@Inject()
|
@Inject()
|
||||||
protected readonly featureFlagService: FeatureFlagService;
|
|
||||||
@Inject()
|
|
||||||
protected readonly permissionsService: PermissionsService;
|
protected readonly permissionsService: PermissionsService;
|
||||||
|
@Inject()
|
||||||
|
protected readonly userRoleService: UserRoleService;
|
||||||
|
|
||||||
public async execute(
|
public async execute(
|
||||||
args: Input,
|
args: Input,
|
||||||
@ -82,18 +84,24 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
|
|
||||||
await this.validate(args, options);
|
await this.validate(args, options);
|
||||||
|
|
||||||
const featureFlagsMap =
|
const dataSource =
|
||||||
await this.featureFlagService.getWorkspaceFeatureFlagsMap(
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
||||||
authContext.workspace.id,
|
authContext.workspace.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const featureFlagsMap = dataSource.featureFlagMap;
|
||||||
|
|
||||||
|
const isPermissionsV2Enabled =
|
||||||
|
featureFlagsMap[FeatureFlagKey.IsPermissionsV2Enabled];
|
||||||
|
|
||||||
if (objectMetadataItemWithFieldMaps.isSystem === true) {
|
if (objectMetadataItemWithFieldMaps.isSystem === true) {
|
||||||
await this.validateSystemObjectPermissionsOrThrow(options);
|
await this.validateSystemObjectPermissionsOrThrow(options);
|
||||||
} else {
|
} else {
|
||||||
await this.validateObjectRecordPermissionsOrThrow({
|
if (!isPermissionsV2Enabled)
|
||||||
operationName,
|
await this.validateObjectRecordPermissionsOrThrow({
|
||||||
options,
|
operationName,
|
||||||
});
|
options,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const hookedArgs =
|
const hookedArgs =
|
||||||
@ -110,13 +118,14 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
ResolverArgsType[capitalize(operationName)],
|
ResolverArgsType[capitalize(operationName)],
|
||||||
)) as Input;
|
)) as Input;
|
||||||
|
|
||||||
const dataSource =
|
const roleId = await this.userRoleService.getRoleIdForUserWorkspace({
|
||||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace(
|
userWorkspaceId: authContext.userWorkspaceId,
|
||||||
authContext.workspace.id,
|
workspaceId: authContext.workspace.id,
|
||||||
);
|
});
|
||||||
|
|
||||||
const repository = dataSource.getRepository(
|
const repository = dataSource.getRepository(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
|
roleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const graphqlQueryParser = new GraphqlQueryParser(
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
@ -140,6 +149,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
repository,
|
repository,
|
||||||
graphqlQueryParser,
|
graphqlQueryParser,
|
||||||
graphqlQuerySelectedFieldsResult,
|
graphqlQuerySelectedFieldsResult,
|
||||||
|
roleId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const results = await this.resolve(
|
const results = await this.resolve(
|
||||||
|
|||||||
@ -35,6 +35,8 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const objectRecords = await this.insertOrUpsertRecords(executionArgs);
|
const objectRecords = await this.insertOrUpsertRecords(executionArgs);
|
||||||
|
|
||||||
const upsertedRecords = await this.fetchUpsertedRecords(
|
const upsertedRecords = await this.fetchUpsertedRecords(
|
||||||
@ -56,6 +58,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
featureFlagsMap,
|
featureFlagsMap,
|
||||||
|
roleId,
|
||||||
);
|
);
|
||||||
|
|
||||||
return this.formatRecordsForResponse(
|
return this.formatRecordsForResponse(
|
||||||
@ -323,6 +326,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps: ObjectMetadataMaps,
|
objectMetadataMaps: ObjectMetadataMaps,
|
||||||
featureFlagsMap: Record<FeatureFlagKey, boolean>,
|
featureFlagsMap: Record<FeatureFlagKey, boolean>,
|
||||||
|
roleId?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
if (!executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (!executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
return;
|
return;
|
||||||
@ -338,6 +342,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,8 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
const { authContext, objectMetadataMaps, objectMetadataItemWithFieldMaps } =
|
const { authContext, objectMetadataMaps, objectMetadataItemWithFieldMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const objectRecords: InsertResult = !executionArgs.args.upsert
|
const objectRecords: InsertResult = !executionArgs.args.upsert
|
||||||
? await executionArgs.repository.insert(executionArgs.args.data)
|
? await executionArgs.repository.insert(executionArgs.args.data)
|
||||||
: await executionArgs.repository.upsert(executionArgs.args.data, {
|
: await executionArgs.repository.upsert(executionArgs.args.data, {
|
||||||
@ -70,6 +72,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,8 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
|
|||||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
@ -71,6 +73,7 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,8 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
@ -73,6 +75,7 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -26,6 +26,8 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
|
|||||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
@ -69,6 +71,7 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,8 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
|
|||||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
@ -69,6 +71,7 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,6 +47,8 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
|
|||||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
@ -156,6 +158,7 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,8 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver
|
|||||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
@ -79,6 +81,7 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,8 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
|
|||||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
@ -71,6 +73,7 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,8 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol
|
|||||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
@ -73,6 +75,7 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,8 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
@ -108,6 +110,7 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,6 +34,8 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
|
||||||
executionArgs.options;
|
executionArgs.options;
|
||||||
|
|
||||||
|
const { roleId } = executionArgs;
|
||||||
|
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
@ -102,6 +104,7 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
dataSource: executionArgs.dataSource,
|
dataSource: executionArgs.dataSource,
|
||||||
isNewRelationEnabled:
|
isNewRelationEnabled:
|
||||||
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
|
||||||
|
roleId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,11 +2,16 @@ import { Module } from '@nestjs/common';
|
|||||||
|
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
import { WorkspaceFeatureFlagMapCacheModule } from 'src/engine/metadata-modules/workspace-feature-flag-map-cache.service.ts/workspace-roles-feature-flag-map-cache.module';
|
||||||
|
|
||||||
import { LabResolver } from './lab.resolver';
|
import { LabResolver } from './lab.resolver';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [FeatureFlagModule, PermissionsModule],
|
imports: [
|
||||||
|
FeatureFlagModule,
|
||||||
|
PermissionsModule,
|
||||||
|
WorkspaceFeatureFlagMapCacheModule,
|
||||||
|
],
|
||||||
providers: [LabResolver],
|
providers: [LabResolver],
|
||||||
exports: [],
|
exports: [],
|
||||||
})
|
})
|
||||||
|
|||||||
@ -13,12 +13,16 @@ import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions
|
|||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||||
|
import { WorkspaceFeatureFlagMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flag-map-cache.service.ts/workspace-feature-flag-map-cache.service';
|
||||||
|
|
||||||
@Resolver()
|
@Resolver()
|
||||||
@UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter)
|
@UseFilters(AuthGraphqlApiExceptionFilter, PermissionsGraphqlApiExceptionFilter)
|
||||||
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.WORKSPACE))
|
@UseGuards(SettingsPermissionsGuard(SettingPermissionType.WORKSPACE))
|
||||||
export class LabResolver {
|
export class LabResolver {
|
||||||
constructor(private featureFlagService: FeatureFlagService) {}
|
constructor(
|
||||||
|
private featureFlagService: FeatureFlagService,
|
||||||
|
private workspaceFeatureFlagMapCacheService: WorkspaceFeatureFlagMapCacheService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@UseGuards(WorkspaceAuthGuard)
|
@UseGuards(WorkspaceAuthGuard)
|
||||||
@Mutation(() => FeatureFlag)
|
@Mutation(() => FeatureFlag)
|
||||||
@ -27,12 +31,20 @@ export class LabResolver {
|
|||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
): Promise<FeatureFlag> {
|
): Promise<FeatureFlag> {
|
||||||
try {
|
try {
|
||||||
return await this.featureFlagService.upsertWorkspaceFeatureFlag({
|
const result = await this.featureFlagService.upsertWorkspaceFeatureFlag({
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
featureFlag: input.publicFeatureFlag,
|
featureFlag: input.publicFeatureFlag,
|
||||||
value: input.value,
|
value: input.value,
|
||||||
shouldBePublic: true,
|
shouldBePublic: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.workspaceFeatureFlagMapCacheService.recomputeFeatureFlagMapCache(
|
||||||
|
{
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof FeatureFlagException) {
|
if (error instanceof FeatureFlagException) {
|
||||||
throw new UserInputError(error.message);
|
throw new UserInputError(error.message);
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
import { ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||||
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
import { WorkspaceRolesPermissionsCacheModule } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -12,6 +13,7 @@ import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
|||||||
[ObjectPermissionEntity, RoleEntity, ObjectMetadataEntity],
|
[ObjectPermissionEntity, RoleEntity, ObjectMetadataEntity],
|
||||||
'metadata',
|
'metadata',
|
||||||
),
|
),
|
||||||
|
WorkspaceRolesPermissionsCacheModule,
|
||||||
],
|
],
|
||||||
providers: [ObjectPermissionService],
|
providers: [ObjectPermissionService],
|
||||||
exports: [ObjectPermissionService],
|
exports: [ObjectPermissionService],
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
PermissionsExceptionMessage,
|
PermissionsExceptionMessage,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
import { WorkspaceRolesPermissionsCacheService } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.service';
|
||||||
|
|
||||||
export class ObjectPermissionService {
|
export class ObjectPermissionService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -21,6 +22,7 @@ export class ObjectPermissionService {
|
|||||||
private readonly roleRepository: Repository<RoleEntity>,
|
private readonly roleRepository: Repository<RoleEntity>,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
|
private readonly workspaceRolesPermissionsCacheService: WorkspaceRolesPermissionsCacheService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async upsertObjectPermission({
|
public async upsertObjectPermission({
|
||||||
@ -52,6 +54,12 @@ export class ObjectPermissionService {
|
|||||||
throw new Error('Failed to upsert object permission');
|
throw new Error('Failed to upsert object permission');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.workspaceRolesPermissionsCacheService.recomputeRolesPermissionsCache(
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return this.objectPermissionRepository.findOne({
|
return this.objectPermissionRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: objectPermissionId,
|
id: objectPermissionId,
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { RoleService } from 'src/engine/metadata-modules/role/role.service';
|
|||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
import { SettingPermissionModule } from 'src/engine/metadata-modules/setting-permission/setting-permission.module';
|
import { SettingPermissionModule } from 'src/engine/metadata-modules/setting-permission/setting-permission.module';
|
||||||
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.module';
|
||||||
|
import { WorkspaceRolesPermissionsCacheModule } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -24,6 +25,7 @@ import { UserRoleModule } from 'src/engine/metadata-modules/user-role/user-role.
|
|||||||
FeatureFlagModule,
|
FeatureFlagModule,
|
||||||
ObjectPermissionModule,
|
ObjectPermissionModule,
|
||||||
SettingPermissionModule,
|
SettingPermissionModule,
|
||||||
|
WorkspaceRolesPermissionsCacheModule,
|
||||||
],
|
],
|
||||||
providers: [RoleService, RoleResolver],
|
providers: [RoleService, RoleResolver],
|
||||||
exports: [RoleService],
|
exports: [RoleService],
|
||||||
|
|||||||
@ -111,7 +111,7 @@ export class RoleResolver {
|
|||||||
): Promise<RoleDTO> {
|
): Promise<RoleDTO> {
|
||||||
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||||
|
|
||||||
return this.roleService.createRole({
|
return await this.roleService.createRole({
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
input: createRoleInput,
|
input: createRoleInput,
|
||||||
});
|
});
|
||||||
@ -124,10 +124,12 @@ export class RoleResolver {
|
|||||||
): Promise<RoleDTO> {
|
): Promise<RoleDTO> {
|
||||||
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||||
|
|
||||||
return this.roleService.updateRole({
|
const role = await this.roleService.updateRole({
|
||||||
input: updateRoleInput,
|
input: updateRoleInput,
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => String)
|
@Mutation(() => String)
|
||||||
@ -137,7 +139,12 @@ export class RoleResolver {
|
|||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||||
|
|
||||||
return this.roleService.deleteRole(roleId, workspace.id);
|
const deletedRoleId = await this.roleService.deleteRole(
|
||||||
|
roleId,
|
||||||
|
workspace.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
return deletedRoleId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => ObjectPermissionDTO)
|
@Mutation(() => ObjectPermissionDTO)
|
||||||
@ -148,10 +155,13 @@ export class RoleResolver {
|
|||||||
) {
|
) {
|
||||||
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
await this.validatePermissionsV2EnabledOrThrow(workspace);
|
||||||
|
|
||||||
return this.objectPermissionService.upsertObjectPermission({
|
const objectPermission =
|
||||||
workspaceId: workspace.id,
|
await this.objectPermissionService.upsertObjectPermission({
|
||||||
input: upsertObjectPermissionInput,
|
workspaceId: workspace.id,
|
||||||
});
|
input: upsertObjectPermissionInput,
|
||||||
|
});
|
||||||
|
|
||||||
|
return objectPermission;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => [SettingPermissionDTO])
|
@Mutation(() => [SettingPermissionDTO])
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
|||||||
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||||
import { isArgDefinedIfProvidedOrThrow } from 'src/engine/metadata-modules/utils/is-arg-defined-if-provided-or-throw.util';
|
import { isArgDefinedIfProvidedOrThrow } from 'src/engine/metadata-modules/utils/is-arg-defined-if-provided-or-throw.util';
|
||||||
|
import { WorkspaceRolesPermissionsCacheService } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.service';
|
||||||
|
|
||||||
export class RoleService {
|
export class RoleService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -30,6 +31,7 @@ export class RoleService {
|
|||||||
@InjectRepository(UserWorkspaceRoleEntity, 'metadata')
|
@InjectRepository(UserWorkspaceRoleEntity, 'metadata')
|
||||||
private readonly userWorkspaceRoleRepository: Repository<UserWorkspaceRoleEntity>,
|
private readonly userWorkspaceRoleRepository: Repository<UserWorkspaceRoleEntity>,
|
||||||
private readonly userRoleService: UserRoleService,
|
private readonly userRoleService: UserRoleService,
|
||||||
|
private readonly workspaceRolesPermissionsCacheService: WorkspaceRolesPermissionsCacheService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async getWorkspaceRoles(workspaceId: string): Promise<RoleEntity[]> {
|
public async getWorkspaceRoles(workspaceId: string): Promise<RoleEntity[]> {
|
||||||
@ -63,7 +65,7 @@ export class RoleService {
|
|||||||
}): Promise<RoleEntity> {
|
}): Promise<RoleEntity> {
|
||||||
await this.validateRoleInput({ input, workspaceId });
|
await this.validateRoleInput({ input, workspaceId });
|
||||||
|
|
||||||
return this.roleRepository.save({
|
const role = this.roleRepository.save({
|
||||||
label: input.label,
|
label: input.label,
|
||||||
description: input.description,
|
description: input.description,
|
||||||
icon: input.icon,
|
icon: input.icon,
|
||||||
@ -75,6 +77,14 @@ export class RoleService {
|
|||||||
isEditable: true,
|
isEditable: true,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.workspaceRolesPermissionsCacheService.recomputeRolesPermissionsCache(
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return role;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateRole({
|
public async updateRole({
|
||||||
@ -114,6 +124,12 @@ export class RoleService {
|
|||||||
...input.update,
|
...input.update,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.workspaceRolesPermissionsCacheService.recomputeRolesPermissionsCache(
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return { ...existingRole, ...updatedRole };
|
return { ...existingRole, ...updatedRole };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,6 +192,12 @@ export class RoleService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.workspaceRolesPermissionsCacheService.recomputeRolesPermissionsCache(
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
return roleId;
|
return roleId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -58,6 +58,24 @@ export class UserRoleService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async getRoleIdForUserWorkspace({
|
||||||
|
workspaceId,
|
||||||
|
userWorkspaceId,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
userWorkspaceId?: string;
|
||||||
|
}): Promise<string | undefined> {
|
||||||
|
if (!isDefined(userWorkspaceId)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userWorkspaceRole = await this.userWorkspaceRoleRepository.findOne({
|
||||||
|
where: { userWorkspaceId, workspaceId },
|
||||||
|
});
|
||||||
|
|
||||||
|
return userWorkspaceRole?.roleId;
|
||||||
|
}
|
||||||
|
|
||||||
public async getRolesByUserWorkspaces({
|
public async getRolesByUserWorkspaces({
|
||||||
userWorkspaceIds,
|
userWorkspaceIds,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||||
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspaceFeatureFlagMapCacheService {
|
||||||
|
logger = new Logger(WorkspaceFeatureFlagMapCacheService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async recomputeFeatureFlagMapCache({
|
||||||
|
workspaceId,
|
||||||
|
ignoreLock = false,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
ignoreLock?: boolean;
|
||||||
|
}): Promise<void> {
|
||||||
|
const isAlreadyCaching =
|
||||||
|
await this.workspaceCacheStorageService.getFeatureFlagMapOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ignoreLock && isAlreadyCaching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.workspaceCacheStorageService.addFeatureFlagMapOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const freshFeatureFlagMap =
|
||||||
|
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
|
||||||
|
|
||||||
|
await this.workspaceCacheStorageService.setFeatureFlagMap(
|
||||||
|
workspaceId,
|
||||||
|
freshFeatureFlagMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.workspaceCacheStorageService.removeFeatureFlagMapOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { WorkspaceFeatureFlagMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flag-map-cache.service.ts/workspace-feature-flag-map-cache.service';
|
||||||
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([Workspace], 'core'),
|
||||||
|
WorkspaceCacheStorageModule,
|
||||||
|
FeatureFlagModule,
|
||||||
|
],
|
||||||
|
providers: [WorkspaceFeatureFlagMapCacheService],
|
||||||
|
exports: [WorkspaceFeatureFlagMapCacheService],
|
||||||
|
})
|
||||||
|
export class WorkspaceFeatureFlagMapCacheModule {}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||||
|
|
||||||
|
import { WorkspaceRolesPermissionsCacheService } from './workspace-roles-permissions-cache.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([Workspace], 'core'),
|
||||||
|
TypeOrmModule.forFeature([ObjectMetadataEntity, RoleEntity], 'metadata'),
|
||||||
|
WorkspaceCacheStorageModule,
|
||||||
|
],
|
||||||
|
providers: [WorkspaceRolesPermissionsCacheService],
|
||||||
|
exports: [WorkspaceRolesPermissionsCacheService],
|
||||||
|
})
|
||||||
|
export class WorkspaceRolesPermissionsCacheModule {}
|
||||||
@ -0,0 +1,128 @@
|
|||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ObjectRecordsPermissions,
|
||||||
|
ObjectRecordsPermissionsByRoleId,
|
||||||
|
} from 'twenty-shared/types';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspaceRolesPermissionsCacheService {
|
||||||
|
logger = new Logger(WorkspaceRolesPermissionsCacheService.name);
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
|
@InjectRepository(RoleEntity, 'metadata')
|
||||||
|
private readonly roleRepository: Repository<RoleEntity>,
|
||||||
|
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async recomputeRolesPermissionsCache({
|
||||||
|
workspaceId,
|
||||||
|
ignoreLock = false,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
ignoreLock?: boolean;
|
||||||
|
}): Promise<void> {
|
||||||
|
const isAlreadyCaching =
|
||||||
|
await this.workspaceCacheStorageService.getRolesPermissionsOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ignoreLock && isAlreadyCaching) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.workspaceCacheStorageService.addRolesPermissionsOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const freshObjectRecordsPermissionsByRoleId =
|
||||||
|
await this.getObjectRecordPermissionsForRoles({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.workspaceCacheStorageService.setRolesPermissions(
|
||||||
|
workspaceId,
|
||||||
|
freshObjectRecordsPermissionsByRoleId,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.workspaceCacheStorageService.removeRolesPermissionsOngoingCachingLock(
|
||||||
|
workspaceId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getObjectRecordPermissionsForRoles({
|
||||||
|
workspaceId,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
}): Promise<ObjectRecordsPermissionsByRoleId> {
|
||||||
|
const roles = await this.roleRepository.find({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const workspaceObjectMetadataNames =
|
||||||
|
await this.getWorkspaceObjectMetadataNames(workspaceId);
|
||||||
|
|
||||||
|
const permissionsByRoleId: ObjectRecordsPermissionsByRoleId = {};
|
||||||
|
|
||||||
|
for (const role of roles) {
|
||||||
|
const objectRecordsPermissions: ObjectRecordsPermissions = {};
|
||||||
|
|
||||||
|
for (const objectMetadataNameSingular of workspaceObjectMetadataNames) {
|
||||||
|
objectRecordsPermissions[objectMetadataNameSingular] = {
|
||||||
|
canRead: role.canReadAllObjectRecords,
|
||||||
|
canUpdate: role.canUpdateAllObjectRecords,
|
||||||
|
canSoftDelete: role.canSoftDeleteAllObjectRecords,
|
||||||
|
canDestroy: role.canDestroyAllObjectRecords,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionsByRoleId[role.id] = objectRecordsPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
return permissionsByRoleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getWorkspaceObjectMetadataNames(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<string[]> {
|
||||||
|
let workspaceObjectMetadataNames: string[] = [];
|
||||||
|
const metadataVersion =
|
||||||
|
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
||||||
|
|
||||||
|
if (metadataVersion) {
|
||||||
|
const objectMetadataMaps =
|
||||||
|
await this.workspaceCacheStorageService.getObjectMetadataMaps(
|
||||||
|
workspaceId,
|
||||||
|
metadataVersion,
|
||||||
|
);
|
||||||
|
|
||||||
|
workspaceObjectMetadataNames = Object.values(
|
||||||
|
objectMetadataMaps?.byId ?? {},
|
||||||
|
).map((objectMetadata) => objectMetadata.nameSingular);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!metadataVersion || workspaceObjectMetadataNames.length === 0) {
|
||||||
|
const workspaceObjectMetadata = await this.objectMetadataRepository.find({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
workspaceObjectMetadataNames = workspaceObjectMetadata.map(
|
||||||
|
(objectMetadata) => objectMetadata.nameSingular,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return workspaceObjectMetadataNames;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { ObjectRecordsPermissionsByRoleId } from 'twenty-shared/types';
|
||||||
import {
|
import {
|
||||||
DataSource,
|
DataSource,
|
||||||
DataSourceOptions,
|
DataSourceOptions,
|
||||||
@ -6,6 +7,7 @@ import {
|
|||||||
QueryRunner,
|
QueryRunner,
|
||||||
} from 'typeorm';
|
} 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 { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/entity.manager';
|
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/entity.manager';
|
||||||
@ -14,20 +16,37 @@ import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.
|
|||||||
export class WorkspaceDataSource extends DataSource {
|
export class WorkspaceDataSource extends DataSource {
|
||||||
readonly internalContext: WorkspaceInternalContext;
|
readonly internalContext: WorkspaceInternalContext;
|
||||||
readonly manager: WorkspaceEntityManager;
|
readonly manager: WorkspaceEntityManager;
|
||||||
|
featureFlagMapVersion: string;
|
||||||
|
featureFlagMap: FeatureFlagMap;
|
||||||
|
rolesPermissionsVersion?: string;
|
||||||
|
permissionsPerRoleId?: ObjectRecordsPermissionsByRoleId;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
internalContext: WorkspaceInternalContext,
|
internalContext: WorkspaceInternalContext,
|
||||||
options: DataSourceOptions,
|
options: DataSourceOptions,
|
||||||
|
featureFlagMapVersion: string,
|
||||||
|
featureFlagMap: FeatureFlagMap,
|
||||||
|
rolesPermissionsVersion?: string,
|
||||||
|
permissionsPerRoleId?: ObjectRecordsPermissionsByRoleId,
|
||||||
) {
|
) {
|
||||||
super(options);
|
super(options);
|
||||||
this.internalContext = internalContext;
|
this.internalContext = internalContext;
|
||||||
// Recreate manager after internalContext has been initialized
|
// Recreate manager after internalContext has been initialized
|
||||||
this.manager = this.createEntityManager();
|
this.manager = this.createEntityManager();
|
||||||
|
this.featureFlagMap = featureFlagMap;
|
||||||
|
this.featureFlagMapVersion = featureFlagMapVersion;
|
||||||
|
this.rolesPermissionsVersion = rolesPermissionsVersion;
|
||||||
|
this.permissionsPerRoleId = permissionsPerRoleId;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getRepository<Entity extends ObjectLiteral>(
|
override getRepository<Entity extends ObjectLiteral>(
|
||||||
target: EntityTarget<Entity>,
|
target: EntityTarget<Entity>,
|
||||||
|
roleId?: string,
|
||||||
): WorkspaceRepository<Entity> {
|
): WorkspaceRepository<Entity> {
|
||||||
|
if (roleId) {
|
||||||
|
return this.manager.getRepository(target, roleId);
|
||||||
|
}
|
||||||
|
|
||||||
return this.manager.getRepository(target);
|
return this.manager.getRepository(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,4 +55,20 @@ export class WorkspaceDataSource extends DataSource {
|
|||||||
): WorkspaceEntityManager {
|
): WorkspaceEntityManager {
|
||||||
return new WorkspaceEntityManager(this.internalContext, this, queryRunner);
|
return new WorkspaceEntityManager(this.internalContext, this, queryRunner);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setRolesPermissionsVersion(rolesPermissionsVersion: string) {
|
||||||
|
this.rolesPermissionsVersion = rolesPermissionsVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
setRolesPermissions(permissionsPerRoleId: ObjectRecordsPermissionsByRoleId) {
|
||||||
|
this.permissionsPerRoleId = permissionsPerRoleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFeatureFlagMap(featureFlagMap: FeatureFlagMap) {
|
||||||
|
this.featureFlagMap = featureFlagMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFeatureFlagMapVersion(featureFlagMapVersion: string) {
|
||||||
|
this.featureFlagMapVersion = featureFlagMapVersion;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,14 +4,17 @@ import {
|
|||||||
EntityTarget,
|
EntityTarget,
|
||||||
ObjectLiteral,
|
ObjectLiteral,
|
||||||
QueryRunner,
|
QueryRunner,
|
||||||
|
Repository,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
|
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
|
|
||||||
export class WorkspaceEntityManager extends EntityManager {
|
export class WorkspaceEntityManager extends EntityManager {
|
||||||
private readonly internalContext: WorkspaceInternalContext;
|
private readonly internalContext: WorkspaceInternalContext;
|
||||||
|
readonly repositories: Map<string, Repository<any>>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
internalContext: WorkspaceInternalContext,
|
internalContext: WorkspaceInternalContext,
|
||||||
@ -20,27 +23,39 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
) {
|
) {
|
||||||
super(connection, queryRunner);
|
super(connection, queryRunner);
|
||||||
this.internalContext = internalContext;
|
this.internalContext = internalContext;
|
||||||
|
this.repositories = new Map();
|
||||||
}
|
}
|
||||||
|
|
||||||
override getRepository<Entity extends ObjectLiteral>(
|
override getRepository<Entity extends ObjectLiteral>(
|
||||||
target: EntityTarget<Entity>,
|
target: EntityTarget<Entity>,
|
||||||
|
roleId?: string,
|
||||||
): WorkspaceRepository<Entity> {
|
): WorkspaceRepository<Entity> {
|
||||||
// find already created repository instance and return it if found
|
const dataSource = this.connection as WorkspaceDataSource;
|
||||||
|
const repositoryKey = `${dataSource.getMetadata(target).name}_${roleId ?? 'default'}${dataSource.rolesPermissionsVersion ? `_${dataSource.rolesPermissionsVersion}` : ''}${dataSource.featureFlagMapVersion ? `_${dataSource.featureFlagMapVersion}` : ''}`;
|
||||||
const repoFromMap = this.repositories.get(target);
|
const repoFromMap = this.repositories.get(repositoryKey);
|
||||||
|
|
||||||
if (repoFromMap) {
|
if (repoFromMap) {
|
||||||
return repoFromMap as WorkspaceRepository<Entity>;
|
return repoFromMap as WorkspaceRepository<Entity>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let objectPermissions = {};
|
||||||
|
|
||||||
|
if (roleId) {
|
||||||
|
const objectPermissionsByRoleId = dataSource.permissionsPerRoleId;
|
||||||
|
|
||||||
|
objectPermissions = objectPermissionsByRoleId?.[roleId] ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
const newRepository = new WorkspaceRepository<Entity>(
|
const newRepository = new WorkspaceRepository<Entity>(
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
target,
|
target,
|
||||||
this,
|
this,
|
||||||
|
dataSource.featureFlagMap,
|
||||||
this.queryRunner,
|
this.queryRunner,
|
||||||
|
objectPermissions,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.repositories.set(target, newRepository);
|
this.repositories.set(repositoryKey, newRepository);
|
||||||
|
|
||||||
return newRepository;
|
return newRepository;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,4 +11,6 @@ export enum TwentyORMExceptionCode {
|
|||||||
METADATA_VERSION_MISMATCH = 'METADATA_VERSION_MISMATCH',
|
METADATA_VERSION_MISMATCH = 'METADATA_VERSION_MISMATCH',
|
||||||
METADATA_COLLECTION_NOT_FOUND = 'METADATA_COLLECTION_NOT_FOUND',
|
METADATA_COLLECTION_NOT_FOUND = 'METADATA_COLLECTION_NOT_FOUND',
|
||||||
WORKSPACE_SCHEMA_NOT_FOUND = 'WORKSPACE_SCHEMA_NOT_FOUND',
|
WORKSPACE_SCHEMA_NOT_FOUND = 'WORKSPACE_SCHEMA_NOT_FOUND',
|
||||||
|
ROLES_PERMISSIONS_VERSION_NOT_FOUND = 'ROLES_PERMISSIONS_VERSION_NOT_FOUND',
|
||||||
|
FEATURE_FLAG_MAP_VERSION_NOT_FOUND = 'FEATURE_FLAG_MAP_VERSION_NOT_FOUND',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ export class ScopedWorkspaceContextFactory {
|
|||||||
public create(): {
|
public create(): {
|
||||||
workspaceId: string | null;
|
workspaceId: string | null;
|
||||||
workspaceMetadataVersion: number | null;
|
workspaceMetadataVersion: number | null;
|
||||||
|
userWorkspaceId: string | null;
|
||||||
} {
|
} {
|
||||||
const workspaceId: string | undefined =
|
const workspaceId: string | undefined =
|
||||||
this.request?.['req']?.['workspaceId'] ||
|
this.request?.['req']?.['workspaceId'] ||
|
||||||
@ -22,6 +23,7 @@ export class ScopedWorkspaceContextFactory {
|
|||||||
return {
|
return {
|
||||||
workspaceId: workspaceId ?? null,
|
workspaceId: workspaceId ?? null,
|
||||||
workspaceMetadataVersion: workspaceMetadataVersion ?? null,
|
workspaceMetadataVersion: workspaceMetadataVersion ?? null,
|
||||||
|
userWorkspaceId: this.request?.['req']?.['userWorkspaceId'] ?? null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,18 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ObjectRecordsPermissionsByRoleId } from 'twenty-shared/types';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { EntitySchema } from 'typeorm';
|
import { EntitySchema } from 'typeorm';
|
||||||
|
|
||||||
|
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||||
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
|
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
|
||||||
|
|
||||||
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||||
|
import { WorkspaceFeatureFlagMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flag-map-cache.service.ts/workspace-feature-flag-map-cache.service';
|
||||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||||
|
import { WorkspaceRolesPermissionsCacheService } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.service';
|
||||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||||
import {
|
import {
|
||||||
TwentyORMException,
|
TwentyORMException,
|
||||||
@ -17,6 +23,11 @@ import { PromiseMemoizer } from 'src/engine/twenty-orm/storage/promise-memoizer.
|
|||||||
import { CacheKey } from 'src/engine/twenty-orm/storage/types/cache-key.type';
|
import { CacheKey } from 'src/engine/twenty-orm/storage/types/cache-key.type';
|
||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
|
||||||
|
type CacheResult<T, U> = {
|
||||||
|
version: T;
|
||||||
|
data: U;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceDatasourceFactory {
|
export class WorkspaceDatasourceFactory {
|
||||||
private readonly logger = new Logger(WorkspaceDatasourceFactory.name);
|
private readonly logger = new Logger(WorkspaceDatasourceFactory.name);
|
||||||
@ -28,19 +39,35 @@ export class WorkspaceDatasourceFactory {
|
|||||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||||
private readonly entitySchemaFactory: EntitySchemaFactory,
|
private readonly entitySchemaFactory: EntitySchemaFactory,
|
||||||
|
private readonly workspaceRolesPermissionsCacheService: WorkspaceRolesPermissionsCacheService,
|
||||||
|
private readonly workspaceFeatureFlagMapCacheService: WorkspaceFeatureFlagMapCacheService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async create(
|
public async create(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
workspaceMetadataVersion: number | null,
|
workspaceMetadataVersion: number | null,
|
||||||
failOnMetadataCacheMiss = true,
|
shouldFailIfMetadataNotFound = true,
|
||||||
): Promise<WorkspaceDataSource> {
|
): Promise<WorkspaceDataSource> {
|
||||||
const cachedWorkspaceMetadataVersion =
|
const cachedWorkspaceMetadataVersion =
|
||||||
await this.getWorkspaceMetadataVersionFromCache(
|
await this.getWorkspaceMetadataVersionFromCache(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
failOnMetadataCacheMiss,
|
shouldFailIfMetadataNotFound,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { data: cachedFeatureFlagMap, version: cachedFeatureFlagMapVersion } =
|
||||||
|
await this.getFeatureFlagMapFromCache({ workspaceId });
|
||||||
|
|
||||||
|
const isPermissionsV2Enabled =
|
||||||
|
cachedFeatureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
|
||||||
|
|
||||||
|
const {
|
||||||
|
data: cachedRolesPermissions,
|
||||||
|
version: cachedRolesPermissionsVersion,
|
||||||
|
} = await this.getRolesPermissionsFromCache({
|
||||||
|
workspaceId,
|
||||||
|
isPermissionsV2Enabled,
|
||||||
|
});
|
||||||
|
|
||||||
if (
|
if (
|
||||||
workspaceMetadataVersion !== null &&
|
workspaceMetadataVersion !== null &&
|
||||||
cachedWorkspaceMetadataVersion !== workspaceMetadataVersion
|
cachedWorkspaceMetadataVersion !== workspaceMetadataVersion
|
||||||
@ -139,6 +166,10 @@ export class WorkspaceDatasourceFactory {
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
},
|
},
|
||||||
|
cachedFeatureFlagMapVersion,
|
||||||
|
cachedFeatureFlagMap,
|
||||||
|
cachedRolesPermissionsVersion,
|
||||||
|
cachedRolesPermissions,
|
||||||
);
|
);
|
||||||
|
|
||||||
await workspaceDataSource.initialize();
|
await workspaceDataSource.initialize();
|
||||||
@ -162,28 +193,206 @@ export class WorkspaceDatasourceFactory {
|
|||||||
throw new Error(`Failed to create WorkspaceDataSource for ${cacheKey}`);
|
throw new Error(`Failed to create WorkspaceDataSource for ${cacheKey}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isPermissionsV2Enabled) {
|
||||||
|
await this.updateWorkspaceDataSourceRolesPermissionsIfNeeded({
|
||||||
|
workspaceDataSource,
|
||||||
|
cachedRolesPermissionsVersion,
|
||||||
|
cachedRolesPermissions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.updateWorkspaceDataSourceFeatureFlagMapIfNeeded({
|
||||||
|
workspaceDataSource,
|
||||||
|
cachedFeatureFlagMapVersion,
|
||||||
|
cachedFeatureFlagMap,
|
||||||
|
});
|
||||||
|
|
||||||
return workspaceDataSource;
|
return workspaceDataSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getFromCacheWithRecompute<T, U>({
|
||||||
|
workspaceId,
|
||||||
|
getCacheData,
|
||||||
|
getCacheVersion,
|
||||||
|
recomputeCache,
|
||||||
|
cachedEntityName,
|
||||||
|
exceptionCode,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
getCacheData: (workspaceId: string) => Promise<U | undefined>;
|
||||||
|
getCacheVersion: (workspaceId: string) => Promise<T | undefined>;
|
||||||
|
recomputeCache: (params: { workspaceId: string }) => Promise<void>;
|
||||||
|
cachedEntityName: string;
|
||||||
|
exceptionCode: TwentyORMExceptionCode;
|
||||||
|
}): Promise<CacheResult<T, U>> {
|
||||||
|
let cachedVersion: T | undefined;
|
||||||
|
let cachedData: U | undefined;
|
||||||
|
|
||||||
|
cachedVersion = await getCacheVersion(workspaceId);
|
||||||
|
cachedData = await getCacheData(workspaceId);
|
||||||
|
|
||||||
|
if (!isDefined(cachedData) || !isDefined(cachedVersion)) {
|
||||||
|
await recomputeCache({ workspaceId });
|
||||||
|
|
||||||
|
cachedData = await getCacheData(workspaceId);
|
||||||
|
cachedVersion = await getCacheVersion(workspaceId);
|
||||||
|
|
||||||
|
if (!isDefined(cachedData) || !isDefined(cachedVersion)) {
|
||||||
|
throw new TwentyORMException(
|
||||||
|
`${cachedEntityName} not found after recompute for workspace ${workspaceId}`,
|
||||||
|
exceptionCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: cachedVersion,
|
||||||
|
data: cachedData,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getRolesPermissionsFromCache({
|
||||||
|
workspaceId,
|
||||||
|
isPermissionsV2Enabled,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
isPermissionsV2Enabled?: boolean;
|
||||||
|
}): Promise<
|
||||||
|
CacheResult<
|
||||||
|
string | undefined,
|
||||||
|
ObjectRecordsPermissionsByRoleId | undefined
|
||||||
|
>
|
||||||
|
> {
|
||||||
|
if (!isPermissionsV2Enabled) {
|
||||||
|
return { version: undefined, data: undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.getFromCacheWithRecompute<
|
||||||
|
string | undefined,
|
||||||
|
ObjectRecordsPermissionsByRoleId | undefined
|
||||||
|
>({
|
||||||
|
workspaceId,
|
||||||
|
getCacheData: () =>
|
||||||
|
this.workspaceCacheStorageService.getRolesPermissions(workspaceId),
|
||||||
|
getCacheVersion: () =>
|
||||||
|
this.workspaceCacheStorageService.getRolesPermissionsVersionFromCache(
|
||||||
|
workspaceId,
|
||||||
|
),
|
||||||
|
recomputeCache: (params) =>
|
||||||
|
this.workspaceRolesPermissionsCacheService.recomputeRolesPermissionsCache(
|
||||||
|
params,
|
||||||
|
),
|
||||||
|
cachedEntityName: 'Roles permissions',
|
||||||
|
exceptionCode: TwentyORMExceptionCode.ROLES_PERMISSIONS_VERSION_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getFeatureFlagMapFromCache({
|
||||||
|
workspaceId,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
}): Promise<CacheResult<string, FeatureFlagMap>> {
|
||||||
|
return this.getFromCacheWithRecompute<string, FeatureFlagMap>({
|
||||||
|
workspaceId,
|
||||||
|
getCacheData: () =>
|
||||||
|
this.workspaceCacheStorageService.getFeatureFlagMap(workspaceId),
|
||||||
|
getCacheVersion: () =>
|
||||||
|
this.workspaceCacheStorageService.getFeatureFlagMapVersionFromCache(
|
||||||
|
workspaceId,
|
||||||
|
),
|
||||||
|
recomputeCache: (params) =>
|
||||||
|
this.workspaceFeatureFlagMapCacheService.recomputeFeatureFlagMapCache(
|
||||||
|
params,
|
||||||
|
),
|
||||||
|
cachedEntityName: 'Feature flag map',
|
||||||
|
exceptionCode: TwentyORMExceptionCode.FEATURE_FLAG_MAP_VERSION_NOT_FOUND,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateWorkspaceDataSourceIfNeeded<T>({
|
||||||
|
workspaceDataSource,
|
||||||
|
currentVersion,
|
||||||
|
newVersion,
|
||||||
|
newData,
|
||||||
|
setData,
|
||||||
|
setVersion,
|
||||||
|
}: {
|
||||||
|
workspaceDataSource: WorkspaceDataSource;
|
||||||
|
currentVersion: string | undefined;
|
||||||
|
newVersion: string | undefined;
|
||||||
|
newData: T | undefined;
|
||||||
|
setData: (data: T) => void;
|
||||||
|
setVersion: (version: string) => void;
|
||||||
|
}): void {
|
||||||
|
if (
|
||||||
|
isDefined(newVersion) &&
|
||||||
|
isDefined(newData) &&
|
||||||
|
currentVersion !== newVersion
|
||||||
|
) {
|
||||||
|
workspaceDataSource.manager.repositories.clear();
|
||||||
|
setData(newData);
|
||||||
|
setVersion(newVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateWorkspaceDataSourceRolesPermissionsIfNeeded({
|
||||||
|
workspaceDataSource,
|
||||||
|
cachedRolesPermissionsVersion,
|
||||||
|
cachedRolesPermissions,
|
||||||
|
}: {
|
||||||
|
workspaceDataSource: WorkspaceDataSource;
|
||||||
|
cachedRolesPermissionsVersion: string | undefined;
|
||||||
|
cachedRolesPermissions: ObjectRecordsPermissionsByRoleId | undefined;
|
||||||
|
}): Promise<void> {
|
||||||
|
this.updateWorkspaceDataSourceIfNeeded({
|
||||||
|
workspaceDataSource,
|
||||||
|
currentVersion: workspaceDataSource.rolesPermissionsVersion,
|
||||||
|
newVersion: cachedRolesPermissionsVersion,
|
||||||
|
newData: cachedRolesPermissions,
|
||||||
|
setData: (data) => workspaceDataSource.setRolesPermissions(data),
|
||||||
|
setVersion: (version) =>
|
||||||
|
workspaceDataSource.setRolesPermissionsVersion(version),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateWorkspaceDataSourceFeatureFlagMapIfNeeded({
|
||||||
|
workspaceDataSource,
|
||||||
|
cachedFeatureFlagMapVersion,
|
||||||
|
cachedFeatureFlagMap,
|
||||||
|
}: {
|
||||||
|
workspaceDataSource: WorkspaceDataSource;
|
||||||
|
cachedFeatureFlagMapVersion: string | undefined;
|
||||||
|
cachedFeatureFlagMap: FeatureFlagMap | undefined;
|
||||||
|
}): Promise<void> {
|
||||||
|
this.updateWorkspaceDataSourceIfNeeded({
|
||||||
|
workspaceDataSource,
|
||||||
|
currentVersion: workspaceDataSource.featureFlagMapVersion,
|
||||||
|
newVersion: cachedFeatureFlagMapVersion,
|
||||||
|
newData: cachedFeatureFlagMap,
|
||||||
|
setData: (data) => workspaceDataSource.setFeatureFlagMap(data),
|
||||||
|
setVersion: (version) =>
|
||||||
|
workspaceDataSource.setFeatureFlagMapVersion(version),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async getWorkspaceMetadataVersionFromCache(
|
private async getWorkspaceMetadataVersionFromCache(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
failOnMetadataCacheMiss = true,
|
shouldFailIfMetadataNotFound = true,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
let latestWorkspaceMetadataVersion =
|
let latestWorkspaceMetadataVersion =
|
||||||
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
await this.workspaceCacheStorageService.getMetadataVersion(workspaceId);
|
||||||
|
|
||||||
if (latestWorkspaceMetadataVersion === undefined) {
|
if (latestWorkspaceMetadataVersion === undefined) {
|
||||||
await this.workspaceMetadataCacheService.recomputeMetadataCache({
|
if (shouldFailIfMetadataNotFound) {
|
||||||
workspaceId,
|
|
||||||
ignoreLock: !failOnMetadataCacheMiss,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (failOnMetadataCacheMiss) {
|
|
||||||
throw new TwentyORMException(
|
throw new TwentyORMException(
|
||||||
`Metadata version not found for workspace ${workspaceId}`,
|
`Metadata version not found for workspace ${workspaceId}`,
|
||||||
TwentyORMExceptionCode.METADATA_VERSION_NOT_FOUND,
|
TwentyORMExceptionCode.METADATA_VERSION_NOT_FOUND,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
await this.workspaceMetadataCacheService.recomputeMetadataCache({
|
||||||
|
workspaceId,
|
||||||
|
ignoreLock: !shouldFailIfMetadataNotFound,
|
||||||
|
});
|
||||||
latestWorkspaceMetadataVersion =
|
latestWorkspaceMetadataVersion =
|
||||||
await this.workspaceCacheStorageService.getMetadataVersion(
|
await this.workspaceCacheStorageService.getMetadataVersion(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@ -0,0 +1,69 @@
|
|||||||
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
|
import { QueryExpressionMap } from 'typeorm/query-builder/QueryExpressionMap';
|
||||||
|
|
||||||
|
import {
|
||||||
|
PermissionsException,
|
||||||
|
PermissionsExceptionCode,
|
||||||
|
PermissionsExceptionMessage,
|
||||||
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
|
||||||
|
const getTargetEntityAndOperationType = (expressionMap: QueryExpressionMap) => {
|
||||||
|
const mainEntity = expressionMap.aliases[0].metadata.name;
|
||||||
|
const operationType = expressionMap.queryType;
|
||||||
|
|
||||||
|
return {
|
||||||
|
mainEntity,
|
||||||
|
operationType,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const validateQueryIsPermittedOrThrow = (
|
||||||
|
expressionMap: QueryExpressionMap,
|
||||||
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
|
) => {
|
||||||
|
const { mainEntity, operationType } =
|
||||||
|
getTargetEntityAndOperationType(expressionMap);
|
||||||
|
|
||||||
|
const permissionsForEntity = objectRecordsPermissions[mainEntity];
|
||||||
|
|
||||||
|
switch (operationType) {
|
||||||
|
case 'select':
|
||||||
|
if (!permissionsForEntity?.canRead) {
|
||||||
|
throw new PermissionsException(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'insert':
|
||||||
|
case 'update':
|
||||||
|
if (!permissionsForEntity?.canUpdate) {
|
||||||
|
throw new PermissionsException(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
if (!permissionsForEntity?.canDestroy) {
|
||||||
|
throw new PermissionsException(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'soft-delete':
|
||||||
|
if (!permissionsForEntity?.canSoftDelete) {
|
||||||
|
throw new PermissionsException(
|
||||||
|
PermissionsExceptionMessage.PERMISSION_DENIED,
|
||||||
|
PermissionsExceptionCode.PERMISSION_DENIED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new PermissionsException(
|
||||||
|
PermissionsExceptionMessage.UNKNOWN_OPERATION_NAME,
|
||||||
|
PermissionsExceptionCode.UNKNOWN_OPERATION_NAME,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -0,0 +1,25 @@
|
|||||||
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
|
import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
||||||
|
|
||||||
|
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||||
|
|
||||||
|
export class WorkspaceQueryBuilder<
|
||||||
|
T extends ObjectLiteral,
|
||||||
|
> extends WorkspaceSelectQueryBuilder<T> {
|
||||||
|
constructor(
|
||||||
|
queryBuilder: SelectQueryBuilder<T>,
|
||||||
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
|
) {
|
||||||
|
super(queryBuilder, objectRecordsPermissions);
|
||||||
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
override clone(): this {
|
||||||
|
const clonedQueryBuilder = super.clone();
|
||||||
|
|
||||||
|
return new WorkspaceQueryBuilder(
|
||||||
|
clonedQueryBuilder,
|
||||||
|
this.objectRecordsPermissions,
|
||||||
|
) as this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
|
import { ObjectLiteral, SelectQueryBuilder, UpdateQueryBuilder } from 'typeorm';
|
||||||
|
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||||
|
|
||||||
|
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util';
|
||||||
|
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
||||||
|
|
||||||
|
export class WorkspaceSelectQueryBuilder<
|
||||||
|
T extends ObjectLiteral,
|
||||||
|
> extends SelectQueryBuilder<T> {
|
||||||
|
objectRecordsPermissions: ObjectRecordsPermissions;
|
||||||
|
constructor(
|
||||||
|
queryBuilder: SelectQueryBuilder<T>,
|
||||||
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
|
) {
|
||||||
|
super(queryBuilder);
|
||||||
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
override update(): WorkspaceUpdateQueryBuilder<T>;
|
||||||
|
|
||||||
|
override update(
|
||||||
|
updateSet: QueryDeepPartialEntity<T>,
|
||||||
|
): WorkspaceUpdateQueryBuilder<T>;
|
||||||
|
|
||||||
|
override update(
|
||||||
|
updateSet?: QueryDeepPartialEntity<T>,
|
||||||
|
): UpdateQueryBuilder<T> {
|
||||||
|
const updateQueryBuilder = updateSet
|
||||||
|
? super.update(updateSet)
|
||||||
|
: super.update();
|
||||||
|
|
||||||
|
return new WorkspaceUpdateQueryBuilder<T>(
|
||||||
|
updateQueryBuilder,
|
||||||
|
this.objectRecordsPermissions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
override execute(): Promise<T[]> {
|
||||||
|
validateQueryIsPermittedOrThrow(
|
||||||
|
this.expressionMap,
|
||||||
|
this.objectRecordsPermissions,
|
||||||
|
);
|
||||||
|
|
||||||
|
return super.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
override getMany(): Promise<T[]> {
|
||||||
|
validateQueryIsPermittedOrThrow(
|
||||||
|
this.expressionMap,
|
||||||
|
this.objectRecordsPermissions,
|
||||||
|
);
|
||||||
|
|
||||||
|
return super.getMany();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
|
import { ObjectLiteral, UpdateQueryBuilder, UpdateResult } from 'typeorm';
|
||||||
|
|
||||||
|
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util';
|
||||||
|
|
||||||
|
export class WorkspaceUpdateQueryBuilder<
|
||||||
|
Entity extends ObjectLiteral,
|
||||||
|
> extends UpdateQueryBuilder<Entity> {
|
||||||
|
private objectRecordsPermissions: ObjectRecordsPermissions;
|
||||||
|
constructor(
|
||||||
|
queryBuilder: UpdateQueryBuilder<Entity>,
|
||||||
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
|
) {
|
||||||
|
super(queryBuilder);
|
||||||
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
override execute(): Promise<UpdateResult> {
|
||||||
|
validateQueryIsPermittedOrThrow(
|
||||||
|
this.expressionMap,
|
||||||
|
this.objectRecordsPermissions,
|
||||||
|
);
|
||||||
|
|
||||||
|
return super.execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
import {
|
import {
|
||||||
DeepPartial,
|
DeepPartial,
|
||||||
DeleteResult,
|
DeleteResult,
|
||||||
@ -20,36 +21,70 @@ import { PickKeysByType } from 'typeorm/common/PickKeysByType';
|
|||||||
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';
|
||||||
|
|
||||||
|
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 { 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 { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||||
|
import { WorkspaceQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-query-builder';
|
||||||
import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage';
|
import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage';
|
||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
export class WorkspaceRepository<
|
export class WorkspaceRepository<
|
||||||
Entity extends ObjectLiteral,
|
T extends ObjectLiteral,
|
||||||
> extends Repository<Entity> {
|
> extends Repository<T> {
|
||||||
private readonly internalContext: WorkspaceInternalContext;
|
private readonly internalContext: WorkspaceInternalContext;
|
||||||
|
private featureFlagMap: FeatureFlagMap;
|
||||||
|
private objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
internalContext: WorkspaceInternalContext,
|
internalContext: WorkspaceInternalContext,
|
||||||
target: EntityTarget<Entity>,
|
target: EntityTarget<T>,
|
||||||
manager: EntityManager,
|
manager: EntityManager,
|
||||||
|
featureFlagMap: FeatureFlagMap,
|
||||||
queryRunner?: QueryRunner,
|
queryRunner?: QueryRunner,
|
||||||
|
objectRecordsPermissions?: ObjectRecordsPermissions,
|
||||||
) {
|
) {
|
||||||
super(target, manager, queryRunner);
|
super(target, manager, queryRunner);
|
||||||
this.internalContext = internalContext;
|
this.internalContext = internalContext;
|
||||||
|
this.featureFlagMap = featureFlagMap;
|
||||||
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
|
}
|
||||||
|
|
||||||
|
override createQueryBuilder<U extends T>(
|
||||||
|
alias?: string,
|
||||||
|
queryRunner?: QueryRunner,
|
||||||
|
): WorkspaceQueryBuilder<U> {
|
||||||
|
const queryBuilder = super.createQueryBuilder(
|
||||||
|
alias,
|
||||||
|
queryRunner,
|
||||||
|
) as unknown as WorkspaceQueryBuilder<U>;
|
||||||
|
const isPermissionsV2Enabled =
|
||||||
|
this.featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
|
||||||
|
|
||||||
|
if (!isPermissionsV2Enabled) {
|
||||||
|
return queryBuilder;
|
||||||
|
} else {
|
||||||
|
if (!this.objectRecordsPermissions) {
|
||||||
|
throw new Error('Object records permissions are required');
|
||||||
|
}
|
||||||
|
|
||||||
|
return new WorkspaceQueryBuilder(
|
||||||
|
queryBuilder,
|
||||||
|
this.objectRecordsPermissions,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FIND METHODS
|
* FIND METHODS
|
||||||
*/
|
*/
|
||||||
override async find(
|
override async find(
|
||||||
options?: FindManyOptions<Entity>,
|
options?: FindManyOptions<T>,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<Entity[]> {
|
): 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 result = await manager.find(this.target, computedOptions);
|
||||||
@ -59,9 +94,9 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async findBy(
|
override async findBy(
|
||||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<Entity[]> {
|
): 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 result = await manager.findBy(this.target, computedOptions.where);
|
||||||
@ -71,9 +106,9 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async findAndCount(
|
override async findAndCount(
|
||||||
options?: FindManyOptions<Entity>,
|
options?: FindManyOptions<T>,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<[Entity[], 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 result = await manager.findAndCount(this.target, computedOptions);
|
||||||
@ -83,9 +118,9 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async findAndCountBy(
|
override async findAndCountBy(
|
||||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<[Entity[], 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 result = await manager.findAndCountBy(
|
const result = await manager.findAndCountBy(
|
||||||
@ -98,9 +133,9 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async findOne(
|
override async findOne(
|
||||||
options: FindOneOptions<Entity>,
|
options: FindOneOptions<T>,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<Entity | 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 result = await manager.findOne(this.target, computedOptions);
|
||||||
@ -110,9 +145,9 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async findOneBy(
|
override async findOneBy(
|
||||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<Entity | 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 result = await manager.findOneBy(this.target, computedOptions.where);
|
||||||
@ -122,9 +157,9 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async findOneOrFail(
|
override async findOneOrFail(
|
||||||
options: FindOneOptions<Entity>,
|
options: FindOneOptions<T>,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<Entity> {
|
): 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 result = await manager.findOneOrFail(this.target, computedOptions);
|
||||||
@ -134,9 +169,9 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async findOneByOrFail(
|
override async findOneByOrFail(
|
||||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<Entity> {
|
): 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.findOneByOrFail(
|
const result = await manager.findOneByOrFail(
|
||||||
@ -151,38 +186,38 @@ export class WorkspaceRepository<
|
|||||||
/**
|
/**
|
||||||
* SAVE METHODS
|
* SAVE METHODS
|
||||||
*/
|
*/
|
||||||
override save<T extends DeepPartial<Entity>>(
|
override save<U extends DeepPartial<T>>(
|
||||||
entities: T[],
|
entities: U[],
|
||||||
options: SaveOptions & { reload: false },
|
options: SaveOptions & { reload: false },
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T[]>;
|
): Promise<T[]>;
|
||||||
|
|
||||||
override save<T extends DeepPartial<Entity>>(
|
override save<U extends DeepPartial<T>>(
|
||||||
entities: T[],
|
entities: U[],
|
||||||
options?: SaveOptions,
|
options?: SaveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<(T & Entity)[]>;
|
): Promise<(U & T)[]>;
|
||||||
|
|
||||||
override save<T extends DeepPartial<Entity>>(
|
override save<U extends DeepPartial<T>>(
|
||||||
entity: T,
|
entity: U,
|
||||||
options: SaveOptions & { reload: false },
|
options: SaveOptions & { reload: false },
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T>;
|
): Promise<T>;
|
||||||
|
|
||||||
override save<T extends DeepPartial<Entity>>(
|
override save<U extends DeepPartial<T>>(
|
||||||
entity: T,
|
entity: U,
|
||||||
options?: SaveOptions,
|
options?: SaveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T & Entity>;
|
): Promise<U & T>;
|
||||||
|
|
||||||
override async save<T extends DeepPartial<Entity>>(
|
override async save<U extends DeepPartial<T>>(
|
||||||
entityOrEntities: T | T[],
|
entityOrEntities: U | U[],
|
||||||
options?: SaveOptions,
|
options?: SaveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T | T[]> {
|
): 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: T | T[];
|
let result: U | U[];
|
||||||
|
|
||||||
// Needed becasuse save method has multiple signature, otherwise we will need to do a type assertion
|
// Needed becasuse save method has multiple signature, otherwise we will need to do a type assertion
|
||||||
if (Array.isArray(formattedEntityOrEntities)) {
|
if (Array.isArray(formattedEntityOrEntities)) {
|
||||||
@ -208,22 +243,22 @@ export class WorkspaceRepository<
|
|||||||
* REMOVE METHODS
|
* REMOVE METHODS
|
||||||
*/
|
*/
|
||||||
override remove(
|
override remove(
|
||||||
entities: Entity[],
|
entities: T[],
|
||||||
options?: RemoveOptions,
|
options?: RemoveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<Entity[]>;
|
): Promise<T[]>;
|
||||||
|
|
||||||
override remove(
|
override remove(
|
||||||
entity: Entity,
|
entity: T,
|
||||||
options?: RemoveOptions,
|
options?: RemoveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<Entity>;
|
): Promise<T>;
|
||||||
|
|
||||||
override async remove(
|
override async remove(
|
||||||
entityOrEntities: Entity | Entity[],
|
entityOrEntities: T | T[],
|
||||||
options?: RemoveOptions,
|
options?: RemoveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<Entity | Entity[]> {
|
): 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 result = await manager.remove(
|
const result = await manager.remove(
|
||||||
@ -247,7 +282,7 @@ export class WorkspaceRepository<
|
|||||||
| Date[]
|
| Date[]
|
||||||
| ObjectId
|
| ObjectId
|
||||||
| ObjectId[]
|
| ObjectId[]
|
||||||
| FindOptionsWhere<Entity>,
|
| FindOptionsWhere<T>,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<DeleteResult> {
|
): Promise<DeleteResult> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -259,38 +294,38 @@ export class WorkspaceRepository<
|
|||||||
return manager.delete(this.target, criteria);
|
return manager.delete(this.target, criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
override softRemove<T extends DeepPartial<Entity>>(
|
override softRemove<U extends DeepPartial<T>>(
|
||||||
entities: T[],
|
entities: U[],
|
||||||
options: SaveOptions & { reload: false },
|
options: SaveOptions & { reload: false },
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T[]>;
|
): Promise<T[]>;
|
||||||
|
|
||||||
override softRemove<T extends DeepPartial<Entity>>(
|
override softRemove<U extends DeepPartial<T>>(
|
||||||
entities: T[],
|
entities: U[],
|
||||||
options?: SaveOptions,
|
options?: SaveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<(T & Entity)[]>;
|
): Promise<(U & T)[]>;
|
||||||
|
|
||||||
override softRemove<T extends DeepPartial<Entity>>(
|
override softRemove<U extends DeepPartial<T>>(
|
||||||
entity: T,
|
entity: U,
|
||||||
options: SaveOptions & { reload: false },
|
options: SaveOptions & { reload: false },
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T>;
|
): Promise<U>;
|
||||||
|
|
||||||
override softRemove<T extends DeepPartial<Entity>>(
|
override softRemove<U extends DeepPartial<T>>(
|
||||||
entity: T,
|
entity: T,
|
||||||
options?: SaveOptions,
|
options?: SaveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T & Entity>;
|
): Promise<U & T>;
|
||||||
|
|
||||||
override async softRemove<T extends DeepPartial<Entity>>(
|
override async softRemove<U extends DeepPartial<T>>(
|
||||||
entityOrEntities: T | T[],
|
entityOrEntities: U | U[],
|
||||||
options?: SaveOptions,
|
options?: SaveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T | T[]> {
|
): 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: T | T[];
|
let result: U | U[];
|
||||||
|
|
||||||
// Needed becasuse save method has multiple signature, otherwise we will need to do a type assertion
|
// Needed becasuse save method has multiple signature, otherwise we will need to do a type assertion
|
||||||
if (Array.isArray(formattedEntityOrEntities)) {
|
if (Array.isArray(formattedEntityOrEntities)) {
|
||||||
@ -322,7 +357,7 @@ export class WorkspaceRepository<
|
|||||||
| Date[]
|
| Date[]
|
||||||
| ObjectId
|
| ObjectId
|
||||||
| ObjectId[]
|
| ObjectId[]
|
||||||
| FindOptionsWhere<Entity>,
|
| FindOptionsWhere<T>,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -337,38 +372,38 @@ export class WorkspaceRepository<
|
|||||||
/**
|
/**
|
||||||
* RECOVERY METHODS
|
* RECOVERY METHODS
|
||||||
*/
|
*/
|
||||||
override recover<T extends DeepPartial<Entity>>(
|
override recover<U extends DeepPartial<T>>(
|
||||||
entities: T[],
|
entities: U,
|
||||||
options: SaveOptions & { reload: false },
|
options: SaveOptions & { reload: false },
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T[]>;
|
): Promise<U>;
|
||||||
|
|
||||||
override recover<T extends DeepPartial<Entity>>(
|
override recover<U extends DeepPartial<T>>(
|
||||||
entities: T[],
|
entities: U,
|
||||||
options?: SaveOptions,
|
options?: SaveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<(T & Entity)[]>;
|
): Promise<(U & T)[]>;
|
||||||
|
|
||||||
override recover<T extends DeepPartial<Entity>>(
|
override recover<U extends DeepPartial<T>>(
|
||||||
entity: T,
|
entity: U,
|
||||||
options: SaveOptions & { reload: false },
|
options: SaveOptions & { reload: false },
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T>;
|
): Promise<U>;
|
||||||
|
|
||||||
override recover<T extends DeepPartial<Entity>>(
|
override recover<U extends DeepPartial<T>>(
|
||||||
entity: T,
|
entity: U,
|
||||||
options?: SaveOptions,
|
options?: SaveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T & Entity>;
|
): Promise<U & T>;
|
||||||
|
|
||||||
override async recover<T extends DeepPartial<Entity>>(
|
override async recover<U extends DeepPartial<T>>(
|
||||||
entityOrEntities: T | T[],
|
entityOrEntities: U | U[],
|
||||||
options?: SaveOptions,
|
options?: SaveOptions,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<T | T[]> {
|
): 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: T | T[];
|
let result: U | U[];
|
||||||
|
|
||||||
// Needed becasuse save method has multiple signature, otherwise we will need to do a type assertion
|
// Needed becasuse save method has multiple signature, otherwise we will need to do a type assertion
|
||||||
if (Array.isArray(formattedEntityOrEntities)) {
|
if (Array.isArray(formattedEntityOrEntities)) {
|
||||||
@ -400,7 +435,7 @@ export class WorkspaceRepository<
|
|||||||
| Date[]
|
| Date[]
|
||||||
| ObjectId
|
| ObjectId
|
||||||
| ObjectId[]
|
| ObjectId[]
|
||||||
| FindOptionsWhere<Entity>,
|
| FindOptionsWhere<T>,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -416,7 +451,7 @@ export class WorkspaceRepository<
|
|||||||
* INSERT METHODS
|
* INSERT METHODS
|
||||||
*/
|
*/
|
||||||
override async insert(
|
override async insert(
|
||||||
entity: QueryDeepPartialEntity<Entity> | QueryDeepPartialEntity<Entity>[],
|
entity: QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[],
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<InsertResult> {
|
): Promise<InsertResult> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -445,8 +480,8 @@ export class WorkspaceRepository<
|
|||||||
| Date[]
|
| Date[]
|
||||||
| ObjectId
|
| ObjectId
|
||||||
| ObjectId[]
|
| ObjectId[]
|
||||||
| FindOptionsWhere<Entity>,
|
| FindOptionsWhere<T>,
|
||||||
partialEntity: QueryDeepPartialEntity<Entity>,
|
partialEntity: QueryDeepPartialEntity<T>,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<UpdateResult> {
|
): Promise<UpdateResult> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -459,10 +494,8 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async upsert(
|
override async upsert(
|
||||||
entityOrEntities:
|
entityOrEntities: QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[],
|
||||||
| QueryDeepPartialEntity<Entity>
|
conflictPathsOrOptions: string[] | UpsertOptions<T>,
|
||||||
| QueryDeepPartialEntity<Entity>[],
|
|
||||||
conflictPathsOrOptions: string[] | UpsertOptions<Entity>,
|
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<InsertResult> {
|
): Promise<InsertResult> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -488,7 +521,7 @@ export class WorkspaceRepository<
|
|||||||
* EXIST METHODS
|
* EXIST METHODS
|
||||||
*/
|
*/
|
||||||
override async exists(
|
override async exists(
|
||||||
options?: FindManyOptions<Entity>,
|
options?: FindManyOptions<T>,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -498,7 +531,7 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async existsBy(
|
override async existsBy(
|
||||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -511,7 +544,7 @@ export class WorkspaceRepository<
|
|||||||
* COUNT METHODS
|
* COUNT METHODS
|
||||||
*/
|
*/
|
||||||
override async count(
|
override async count(
|
||||||
options?: FindManyOptions<Entity>,
|
options?: FindManyOptions<T>,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -521,7 +554,7 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async countBy(
|
override async countBy(
|
||||||
where: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -534,8 +567,8 @@ export class WorkspaceRepository<
|
|||||||
* MATH METHODS
|
* MATH METHODS
|
||||||
*/
|
*/
|
||||||
override async sum(
|
override async sum(
|
||||||
columnName: PickKeysByType<Entity, number>,
|
columnName: PickKeysByType<T, number>,
|
||||||
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<number | null> {
|
): Promise<number | null> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -545,8 +578,8 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async average(
|
override async average(
|
||||||
columnName: PickKeysByType<Entity, number>,
|
columnName: PickKeysByType<T, number>,
|
||||||
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<number | null> {
|
): Promise<number | null> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -556,8 +589,8 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async minimum(
|
override async minimum(
|
||||||
columnName: PickKeysByType<Entity, number>,
|
columnName: PickKeysByType<T, number>,
|
||||||
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<number | null> {
|
): Promise<number | null> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -567,8 +600,8 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async maximum(
|
override async maximum(
|
||||||
columnName: PickKeysByType<Entity, number>,
|
columnName: PickKeysByType<T, number>,
|
||||||
where?: FindOptionsWhere<Entity> | FindOptionsWhere<Entity>[],
|
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
): Promise<number | null> {
|
): Promise<number | null> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
@ -578,7 +611,7 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async increment(
|
override async increment(
|
||||||
conditions: FindOptionsWhere<Entity>,
|
conditions: FindOptionsWhere<T>,
|
||||||
propertyPath: string,
|
propertyPath: string,
|
||||||
value: number | string,
|
value: number | string,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
@ -597,7 +630,7 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
override async decrement(
|
override async decrement(
|
||||||
conditions: FindOptionsWhere<Entity>,
|
conditions: FindOptionsWhere<T>,
|
||||||
propertyPath: string,
|
propertyPath: string,
|
||||||
value: number | string,
|
value: number | string,
|
||||||
entityManager?: EntityManager,
|
entityManager?: EntityManager,
|
||||||
@ -652,8 +685,8 @@ export class WorkspaceRepository<
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async transformOptions<
|
private async transformOptions<
|
||||||
T extends FindManyOptions<Entity> | FindOneOptions<Entity> | undefined,
|
U extends FindManyOptions<T> | FindOneOptions<T> | undefined,
|
||||||
>(options: T): Promise<T> {
|
>(options: U): Promise<U> {
|
||||||
if (!options) {
|
if (!options) {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,19 +15,19 @@ export class TwentyORMGlobalManager {
|
|||||||
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
workspaceEntity: Type<T>,
|
workspaceEntity: Type<T>,
|
||||||
failOnMetadataCacheMiss?: boolean,
|
shouldFailIfMetadataNotFound?: boolean,
|
||||||
): Promise<WorkspaceRepository<T>>;
|
): Promise<WorkspaceRepository<T>>;
|
||||||
|
|
||||||
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
objectMetadataName: string,
|
objectMetadataName: string,
|
||||||
failOnMetadataCacheMiss?: boolean,
|
shouldFailIfMetadataNotFound?: boolean,
|
||||||
): Promise<WorkspaceRepository<T>>;
|
): Promise<WorkspaceRepository<T>>;
|
||||||
|
|
||||||
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
async getRepositoryForWorkspace<T extends ObjectLiteral>(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
workspaceEntityOrobjectMetadataName: Type<T> | string,
|
workspaceEntityOrobjectMetadataName: Type<T> | string,
|
||||||
failOnMetadataCacheMiss = true,
|
shouldFailIfMetadataNotFound = true,
|
||||||
): Promise<WorkspaceRepository<T>> {
|
): Promise<WorkspaceRepository<T>> {
|
||||||
let objectMetadataName: string;
|
let objectMetadataName: string;
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ export class TwentyORMGlobalManager {
|
|||||||
const workspaceDataSource = await this.workspaceDataSourceFactory.create(
|
const workspaceDataSource = await this.workspaceDataSourceFactory.create(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
null,
|
null,
|
||||||
failOnMetadataCacheMiss,
|
shouldFailIfMetadataNotFound,
|
||||||
);
|
);
|
||||||
|
|
||||||
const repository = workspaceDataSource.getRepository<T>(objectMetadataName);
|
const repository = workspaceDataSource.getRepository<T>(objectMetadataName);
|
||||||
@ -52,12 +52,12 @@ export class TwentyORMGlobalManager {
|
|||||||
|
|
||||||
async getDataSourceForWorkspace(
|
async getDataSourceForWorkspace(
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
failOnMetadataCacheMiss = true,
|
shouldFailIfMetadataNotFound = true,
|
||||||
) {
|
) {
|
||||||
return await this.workspaceDataSourceFactory.create(
|
return await this.workspaceDataSourceFactory.create(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
null,
|
null,
|
||||||
failOnMetadataCacheMiss,
|
shouldFailIfMetadataNotFound,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import { Injectable, Type } from '@nestjs/common';
|
import { Injectable, Type } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { ObjectLiteral } from 'typeorm';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { ObjectLiteral, Repository } from 'typeorm';
|
||||||
|
|
||||||
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { WorkspaceDatasourceFactory } from 'src/engine/twenty-orm/factories/workspace-datasource.factory';
|
import { WorkspaceDatasourceFactory } from 'src/engine/twenty-orm/factories/workspace-datasource.factory';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
@ -10,6 +13,8 @@ import { convertClassNameToObjectMetadataName } from 'src/engine/workspace-manag
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class TwentyORMManager {
|
export class TwentyORMManager {
|
||||||
constructor(
|
constructor(
|
||||||
|
@InjectRepository(UserWorkspaceRoleEntity, 'metadata')
|
||||||
|
private readonly userWorkspaceRoleRepository: Repository<UserWorkspaceRoleEntity>,
|
||||||
private readonly workspaceDataSourceFactory: WorkspaceDatasourceFactory,
|
private readonly workspaceDataSourceFactory: WorkspaceDatasourceFactory,
|
||||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||||
) {}
|
) {}
|
||||||
@ -25,7 +30,7 @@ export class TwentyORMManager {
|
|||||||
async getRepository<T extends ObjectLiteral>(
|
async getRepository<T extends ObjectLiteral>(
|
||||||
workspaceEntityOrobjectMetadataName: Type<T> | string,
|
workspaceEntityOrobjectMetadataName: Type<T> | string,
|
||||||
): Promise<WorkspaceRepository<T>> {
|
): Promise<WorkspaceRepository<T>> {
|
||||||
const { workspaceId, workspaceMetadataVersion } =
|
const { workspaceId, workspaceMetadataVersion, userWorkspaceId } =
|
||||||
this.scopedWorkspaceContextFactory.create();
|
this.scopedWorkspaceContextFactory.create();
|
||||||
|
|
||||||
let objectMetadataName: string;
|
let objectMetadataName: string;
|
||||||
@ -47,7 +52,20 @@ export class TwentyORMManager {
|
|||||||
workspaceMetadataVersion,
|
workspaceMetadataVersion,
|
||||||
);
|
);
|
||||||
|
|
||||||
return workspaceDataSource.getRepository<T>(objectMetadataName);
|
let roleId: string | undefined;
|
||||||
|
|
||||||
|
if (isDefined(userWorkspaceId)) {
|
||||||
|
const userWorkspaceRole = await this.userWorkspaceRoleRepository.findOne({
|
||||||
|
where: {
|
||||||
|
userWorkspaceId,
|
||||||
|
workspaceId: workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
roleId = userWorkspaceRole?.roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
return workspaceDataSource.getRepository<T>(objectMetadataName, roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDatasource() {
|
async getDatasource() {
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import { Global, Module } from '@nestjs/common';
|
import { Global, Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
|
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
|
||||||
|
import { WorkspaceFeatureFlagMapCacheModule } from 'src/engine/metadata-modules/workspace-feature-flag-map-cache.service.ts/workspace-roles-feature-flag-map-cache.module';
|
||||||
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
import { WorkspaceMetadataCacheModule } from 'src/engine/metadata-modules/workspace-metadata-cache/workspace-metadata-cache.module';
|
||||||
|
import { WorkspaceRolesPermissionsCacheModule } from 'src/engine/metadata-modules/workspace-roles-permissions-cache/workspace-roles-permissions-cache.module';
|
||||||
import { entitySchemaFactories } from 'src/engine/twenty-orm/factories';
|
import { entitySchemaFactories } from 'src/engine/twenty-orm/factories';
|
||||||
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
|
import { EntitySchemaFactory } from 'src/engine/twenty-orm/factories/entity-schema.factory';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
@ -13,10 +18,17 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
|||||||
@Global()
|
@Global()
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
TypeOrmModule.forFeature(
|
||||||
|
[ObjectMetadataEntity, UserWorkspaceRoleEntity],
|
||||||
|
'metadata',
|
||||||
|
),
|
||||||
DataSourceModule,
|
DataSourceModule,
|
||||||
WorkspaceCacheStorageModule,
|
WorkspaceCacheStorageModule,
|
||||||
WorkspaceMetadataCacheModule,
|
WorkspaceMetadataCacheModule,
|
||||||
|
PermissionsModule,
|
||||||
|
WorkspaceRolesPermissionsCacheModule,
|
||||||
|
WorkspaceFeatureFlagMapCacheModule,
|
||||||
|
FeatureFlagModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
...entitySchemaFactories,
|
...entitySchemaFactories,
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import crypto from 'crypto';
|
||||||
|
|
||||||
|
import { ObjectRecordsPermissionsByRoleId } from 'twenty-shared/types';
|
||||||
import { EntitySchemaOptions } from 'typeorm';
|
import { EntitySchemaOptions } from 'typeorm';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
|
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||||
|
|
||||||
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||||
@ -17,6 +23,12 @@ export enum WorkspaceCacheKeys {
|
|||||||
MetadataObjectMetadataMaps = 'metadata:object-metadata-maps',
|
MetadataObjectMetadataMaps = 'metadata:object-metadata-maps',
|
||||||
MetadataObjectMetadataOngoingCachingLock = 'metadata:object-metadata-ongoing-caching-lock',
|
MetadataObjectMetadataOngoingCachingLock = 'metadata:object-metadata-ongoing-caching-lock',
|
||||||
MetadataVersion = 'metadata:workspace-metadata-version',
|
MetadataVersion = 'metadata:workspace-metadata-version',
|
||||||
|
MetadataRolesPermissions = 'metadata:roles-permissions',
|
||||||
|
MetadataRolesPermissionsVersion = 'metadata:roles-permissions-version',
|
||||||
|
MetadataRolesPermissionsOngoingCachingLock = 'metadata:roles-permissions-ongoing-caching-lock',
|
||||||
|
MetadataFeatureFlagMap = 'metadata:feature-flag-map',
|
||||||
|
MetadataFeatureFlagMapVersion = 'metadata:feature-flag-map-version',
|
||||||
|
MetadataFeatureFlagMapOngoingCachingLock = 'metadata:feature-flag-map-ongoing-caching-lock',
|
||||||
}
|
}
|
||||||
|
|
||||||
const TTL_INFINITE = 0;
|
const TTL_INFINITE = 0;
|
||||||
@ -174,6 +186,140 @@ export class WorkspaceCacheStorageService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getRolesPermissionsVersionFromCache(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
return this.cacheStorageService.get<string>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataRolesPermissionsVersion}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setRolesPermissionsVersion(workspaceId: string): Promise<string> {
|
||||||
|
const rolesPermissionsVersion = v4();
|
||||||
|
|
||||||
|
await this.cacheStorageService.set<string>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataRolesPermissionsVersion}:${workspaceId}`,
|
||||||
|
rolesPermissionsVersion,
|
||||||
|
TTL_INFINITE,
|
||||||
|
);
|
||||||
|
|
||||||
|
return rolesPermissionsVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setRolesPermissions(
|
||||||
|
workspaceId: string,
|
||||||
|
permissions: ObjectRecordsPermissionsByRoleId,
|
||||||
|
): Promise<{
|
||||||
|
newRolesPermissionsVersion: string;
|
||||||
|
}> {
|
||||||
|
const [, newRolesPermissionsVersion] = await Promise.all([
|
||||||
|
this.cacheStorageService.set<ObjectRecordsPermissionsByRoleId>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataRolesPermissions}:${workspaceId}`,
|
||||||
|
permissions,
|
||||||
|
TTL_INFINITE,
|
||||||
|
),
|
||||||
|
this.setRolesPermissionsVersion(workspaceId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { newRolesPermissionsVersion };
|
||||||
|
}
|
||||||
|
|
||||||
|
getRolesPermissions(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<ObjectRecordsPermissionsByRoleId | undefined> {
|
||||||
|
return this.cacheStorageService.get<ObjectRecordsPermissionsByRoleId>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataRolesPermissions}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addRolesPermissionsOngoingCachingLock(workspaceId: string) {
|
||||||
|
return this.cacheStorageService.set<boolean>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
||||||
|
true,
|
||||||
|
1_000 * 60, // 1 minute
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeRolesPermissionsOngoingCachingLock(workspaceId: string) {
|
||||||
|
return this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getRolesPermissionsOngoingCachingLock(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<boolean | undefined> {
|
||||||
|
return this.cacheStorageService.get<boolean>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFeatureFlagMapVersionFromCache(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
return this.cacheStorageService.get<string>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataFeatureFlagMapVersion}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async setFeatureFlagMapVersion(workspaceId: string): Promise<string> {
|
||||||
|
const featureFlagMapVersion = crypto.randomUUID();
|
||||||
|
|
||||||
|
await this.cacheStorageService.set<string>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataFeatureFlagMapVersion}:${workspaceId}`,
|
||||||
|
featureFlagMapVersion,
|
||||||
|
TTL_INFINITE,
|
||||||
|
);
|
||||||
|
|
||||||
|
return featureFlagMapVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
async setFeatureFlagMap(
|
||||||
|
workspaceId: string,
|
||||||
|
featureFlagMap: FeatureFlagMap,
|
||||||
|
): Promise<{
|
||||||
|
newFeatureFlagMapVersion: string;
|
||||||
|
}> {
|
||||||
|
const [, newFeatureFlagMapVersion] = await Promise.all([
|
||||||
|
this.cacheStorageService.set<FeatureFlagMap>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataFeatureFlagMap}:${workspaceId}`,
|
||||||
|
featureFlagMap,
|
||||||
|
TTL_INFINITE,
|
||||||
|
),
|
||||||
|
this.setFeatureFlagMapVersion(workspaceId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { newFeatureFlagMapVersion };
|
||||||
|
}
|
||||||
|
|
||||||
|
getFeatureFlagMap(workspaceId: string): Promise<FeatureFlagMap | undefined> {
|
||||||
|
return this.cacheStorageService.get<FeatureFlagMap>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataFeatureFlagMap}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
addFeatureFlagMapOngoingCachingLock(workspaceId: string) {
|
||||||
|
return this.cacheStorageService.set<boolean>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataFeatureFlagMapOngoingCachingLock}:${workspaceId}`,
|
||||||
|
true,
|
||||||
|
1_000 * 60, // 1 minute
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
removeFeatureFlagMapOngoingCachingLock(workspaceId: string) {
|
||||||
|
return this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataFeatureFlagMapOngoingCachingLock}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getFeatureFlagMapOngoingCachingLock(
|
||||||
|
workspaceId: string,
|
||||||
|
): Promise<boolean | undefined> {
|
||||||
|
return this.cacheStorageService.get<boolean>(
|
||||||
|
`${WorkspaceCacheKeys.MetadataFeatureFlagMapOngoingCachingLock}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async flush(workspaceId: string, metadataVersion: number): Promise<void> {
|
async flush(workspaceId: string, metadataVersion: number): Promise<void> {
|
||||||
await this.cacheStorageService.del(
|
await this.cacheStorageService.del(
|
||||||
`${WorkspaceCacheKeys.MetadataObjectMetadataMaps}:${workspaceId}:${metadataVersion}`,
|
`${WorkspaceCacheKeys.MetadataObjectMetadataMaps}:${workspaceId}:${metadataVersion}`,
|
||||||
@ -194,6 +340,30 @@ export class WorkspaceCacheStorageService {
|
|||||||
`${WorkspaceCacheKeys.MetadataObjectMetadataOngoingCachingLock}:${workspaceId}:${metadataVersion}`,
|
`${WorkspaceCacheKeys.MetadataObjectMetadataOngoingCachingLock}:${workspaceId}:${metadataVersion}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataRolesPermissions}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataRolesPermissionsVersion}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataRolesPermissionsOngoingCachingLock}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataFeatureFlagMap}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataFeatureFlagMapVersion}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.cacheStorageService.del(
|
||||||
|
`${WorkspaceCacheKeys.MetadataFeatureFlagMapOngoingCachingLock}:${workspaceId}`,
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: remove this after the feature flag is droped
|
// TODO: remove this after the feature flag is droped
|
||||||
await this.cacheStorageService.del(
|
await this.cacheStorageService.del(
|
||||||
`${FeatureFlagKey.IsNewRelationEnabled}:${workspaceId}`,
|
`${FeatureFlagKey.IsNewRelationEnabled}:${workspaceId}`,
|
||||||
|
|||||||
@ -0,0 +1,8 @@
|
|||||||
|
export type ObjectRecordsPermissions = {
|
||||||
|
[objectName: string]: {
|
||||||
|
canRead: boolean;
|
||||||
|
canUpdate: boolean;
|
||||||
|
canSoftDelete: boolean;
|
||||||
|
canDestroy: boolean;
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
import { ObjectRecordsPermissions } from '@/types';
|
||||||
|
|
||||||
|
export type ObjectRecordsPermissionsByRoleId = {
|
||||||
|
[roleId: string]: ObjectRecordsPermissions;
|
||||||
|
};
|
||||||
@ -10,3 +10,5 @@
|
|||||||
export { ConnectedAccountProvider } from './ConnectedAccountProvider';
|
export { ConnectedAccountProvider } from './ConnectedAccountProvider';
|
||||||
export { FieldMetadataType } from './FieldMetadataType';
|
export { FieldMetadataType } from './FieldMetadataType';
|
||||||
export type { IsExactly } from './IsExactly';
|
export type { IsExactly } from './IsExactly';
|
||||||
|
export type { ObjectRecordsPermissions } from './ObjectRecordsPermissions';
|
||||||
|
export type { ObjectRecordsPermissionsByRoleId } from './ObjectRecordsPermissionsByRoleId';
|
||||||
|
|||||||
Reference in New Issue
Block a user