Code first roles sync (#13667)
This commit is contained in:
+2
@@ -1,5 +1,6 @@
|
||||
import { WorkspaceFieldRelationComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-field-relation.comparator';
|
||||
import { WorkspaceIndexComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-index.comparator';
|
||||
import { WorkspaceRoleComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-role.comparator';
|
||||
|
||||
import { WorkspaceFieldComparator } from './workspace-field.comparator';
|
||||
import { WorkspaceObjectComparator } from './workspace-object.comparator';
|
||||
@@ -9,4 +10,5 @@ export const workspaceSyncMetadataComparators = [
|
||||
WorkspaceFieldRelationComparator,
|
||||
WorkspaceObjectComparator,
|
||||
WorkspaceIndexComparator,
|
||||
WorkspaceRoleComparator,
|
||||
];
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import diff from 'microdiff';
|
||||
import { type FromTo } from 'twenty-shared/types';
|
||||
|
||||
import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
|
||||
|
||||
import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type';
|
||||
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
|
||||
|
||||
type RoleComparatorResult =
|
||||
| {
|
||||
action: ComparatorAction.CREATE;
|
||||
fromFlatRole: null;
|
||||
toFlatRole: FlatRole;
|
||||
}
|
||||
| {
|
||||
action: ComparatorAction.UPDATE;
|
||||
fromFlatRole: FlatRole;
|
||||
toFlatRole: FlatRole;
|
||||
}
|
||||
| {
|
||||
action: ComparatorAction.DELETE;
|
||||
fromFlatRole: FlatRole;
|
||||
toFlatRole: null;
|
||||
};
|
||||
|
||||
type WorkspaceRoleComparatorArgs = FromTo<FlatRole[], 'FlatRoles'>;
|
||||
|
||||
const rolePropertiesToIgnore = [
|
||||
'id',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'workspaceId',
|
||||
'roleTargets',
|
||||
'permissionFlags',
|
||||
'objectPermissions',
|
||||
'fieldPermissions',
|
||||
];
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceRoleComparator {
|
||||
compare({
|
||||
fromFlatRoles,
|
||||
toFlatRoles,
|
||||
}: WorkspaceRoleComparatorArgs): RoleComparatorResult[] {
|
||||
const results: RoleComparatorResult[] = [];
|
||||
|
||||
const keyFactory = (role: FlatRole | RoleEntity) =>
|
||||
role.standardId || role.id;
|
||||
|
||||
const fromRoleMap = transformMetadataForComparison(fromFlatRoles, {
|
||||
shouldIgnoreProperty: (property) =>
|
||||
rolePropertiesToIgnore.includes(property),
|
||||
keyFactory,
|
||||
});
|
||||
|
||||
const toRoleMap = transformMetadataForComparison(toFlatRoles, {
|
||||
shouldIgnoreProperty: (property) =>
|
||||
rolePropertiesToIgnore.includes(property),
|
||||
keyFactory,
|
||||
});
|
||||
|
||||
const roleDifferences = diff(fromRoleMap, toRoleMap);
|
||||
|
||||
for (const difference of roleDifferences) {
|
||||
const uniqueIdentifier = difference.path[0] as string;
|
||||
|
||||
switch (difference.type) {
|
||||
case 'CREATE': {
|
||||
const toRole = toFlatRoles.find(
|
||||
(role) => keyFactory(role) === uniqueIdentifier,
|
||||
);
|
||||
|
||||
if (toRole) {
|
||||
results.push({
|
||||
action: ComparatorAction.CREATE,
|
||||
fromFlatRole: null,
|
||||
toFlatRole: toRole,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'CHANGE': {
|
||||
const fromRole = fromFlatRoles.find(
|
||||
(role) => keyFactory(role) === uniqueIdentifier,
|
||||
);
|
||||
const toRole = toFlatRoles.find(
|
||||
(role) => keyFactory(role) === uniqueIdentifier,
|
||||
);
|
||||
|
||||
if (fromRole && toRole) {
|
||||
results.push({
|
||||
action: ComparatorAction.UPDATE,
|
||||
fromFlatRole: fromRole,
|
||||
toFlatRole: toRole,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'REMOVE': {
|
||||
const fromRole = fromFlatRoles.find(
|
||||
(role) => keyFactory(role) === uniqueIdentifier,
|
||||
);
|
||||
|
||||
if (fromRole && difference.path.length === 1) {
|
||||
results.push({
|
||||
action: ComparatorAction.DELETE,
|
||||
fromFlatRole: fromRole,
|
||||
toFlatRole: null,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
+2
@@ -1,4 +1,5 @@
|
||||
import { StandardIndexFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-index.factory';
|
||||
import { StandardRoleFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-role.factory';
|
||||
|
||||
import { StandardFieldRelationFactory } from './standard-field-relation.factory';
|
||||
import { StandardFieldFactory } from './standard-field.factory';
|
||||
@@ -9,4 +10,5 @@ export const workspaceSyncMetadataFactories = [
|
||||
StandardObjectFactory,
|
||||
StandardFieldRelationFactory,
|
||||
StandardIndexFactory,
|
||||
StandardRoleFactory,
|
||||
];
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type';
|
||||
import { fromStandardRoleDefinitionToFlatRole } from 'src/engine/metadata-modules/flat-role/utils/from-standard-role-definition-to-flat-role.util';
|
||||
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { type StandardRoleDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/types/standard-role-definition.interface';
|
||||
|
||||
@Injectable()
|
||||
export class StandardRoleFactory {
|
||||
create(
|
||||
roleDefinitions: StandardRoleDefinition[],
|
||||
context: WorkspaceSyncContext,
|
||||
existingRoles: RoleEntity[],
|
||||
): FlatRole[] {
|
||||
const computedRoles: FlatRole[] = [];
|
||||
|
||||
for (const roleDefinition of roleDefinitions) {
|
||||
const existingRole = existingRoles.find(
|
||||
(role) => role.standardId === roleDefinition.standardId,
|
||||
);
|
||||
|
||||
const flatRole = fromStandardRoleDefinitionToFlatRole(
|
||||
roleDefinition,
|
||||
context.workspaceId,
|
||||
);
|
||||
|
||||
if (existingRole) {
|
||||
computedRoles.push({
|
||||
...flatRole,
|
||||
id: existingRole.id,
|
||||
uniqueIdentifier: roleDefinition.standardId || existingRole.id,
|
||||
});
|
||||
} else {
|
||||
computedRoles.push({
|
||||
...flatRole,
|
||||
uniqueIdentifier: roleDefinition.standardId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return computedRoles;
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import { IsNull, Not, type EntityManager } from 'typeorm';
|
||||
|
||||
import { ComparatorAction } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
|
||||
import { type WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
import { fromRoleEntityToFlatRole } from 'src/engine/metadata-modules/flat-role/utils/from-role-entity-to-flat-role.util';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { WorkspaceRoleComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-role.comparator';
|
||||
import { StandardRoleFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-role.factory';
|
||||
import { standardRoleDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSyncRoleService {
|
||||
private readonly logger = new Logger(WorkspaceSyncRoleService.name);
|
||||
|
||||
constructor(
|
||||
private readonly standardRoleFactory: StandardRoleFactory,
|
||||
private readonly workspaceRoleComparator: WorkspaceRoleComparator,
|
||||
) {}
|
||||
|
||||
async synchronize(
|
||||
context: WorkspaceSyncContext,
|
||||
manager: EntityManager,
|
||||
): Promise<void> {
|
||||
this.logger.log('Syncing standard role metadata');
|
||||
|
||||
const roleRepository = manager.getRepository(RoleEntity);
|
||||
|
||||
const existingStandardRoleEntities = await roleRepository.find({
|
||||
where: {
|
||||
workspaceId: context.workspaceId,
|
||||
standardId: Not(IsNull()),
|
||||
},
|
||||
});
|
||||
|
||||
const targetStandardRoles = this.standardRoleFactory.create(
|
||||
standardRoleDefinitions,
|
||||
context,
|
||||
existingStandardRoleEntities,
|
||||
);
|
||||
|
||||
const roleComparatorResults = this.workspaceRoleComparator.compare({
|
||||
fromFlatRoles: existingStandardRoleEntities.map(fromRoleEntityToFlatRole),
|
||||
toFlatRoles: targetStandardRoles,
|
||||
});
|
||||
|
||||
for (const roleComparatorResult of roleComparatorResults) {
|
||||
switch (roleComparatorResult.action) {
|
||||
case ComparatorAction.CREATE: {
|
||||
const roleToCreate = roleComparatorResult.toFlatRole;
|
||||
|
||||
const flatRoleData = removePropertiesFromRecord(roleToCreate, [
|
||||
'uniqueIdentifier',
|
||||
'id',
|
||||
]);
|
||||
|
||||
await roleRepository.save({
|
||||
...flatRoleData,
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case ComparatorAction.UPDATE: {
|
||||
const roleToUpdate = roleComparatorResult.toFlatRole;
|
||||
|
||||
const flatRoleData = removePropertiesFromRecord(roleToUpdate, [
|
||||
'id',
|
||||
'uniqueIdentifier',
|
||||
'workspaceId',
|
||||
]);
|
||||
|
||||
await roleRepository.update({ id: roleToUpdate.id }, flatRoleData);
|
||||
break;
|
||||
}
|
||||
|
||||
case ComparatorAction.DELETE: {
|
||||
const roleToDelete = roleComparatorResult.fromFlatRole;
|
||||
|
||||
await roleRepository.delete({ id: roleToDelete.id });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { ADMIN_ROLE } from './roles/admin-role';
|
||||
import { type StandardRoleDefinition } from './types/standard-role-definition.interface';
|
||||
|
||||
export const standardRoleDefinitions = [
|
||||
ADMIN_ROLE,
|
||||
] as const satisfies StandardRoleDefinition[];
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { type StandardRoleDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/types/standard-role-definition.interface';
|
||||
|
||||
export const ADMIN_ROLE: StandardRoleDefinition = {
|
||||
standardId: '20202020-0001-0001-0001-000000000001',
|
||||
label: 'Admin',
|
||||
description: 'Admin role',
|
||||
icon: 'IconUserCog',
|
||||
isEditable: false,
|
||||
canUpdateAllSettings: true,
|
||||
canAccessAllTools: true,
|
||||
canReadAllObjectRecords: true,
|
||||
canUpdateAllObjectRecords: true,
|
||||
canSoftDeleteAllObjectRecords: true,
|
||||
canDestroyAllObjectRecords: true,
|
||||
};
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type';
|
||||
|
||||
export type StandardRoleDefinition = Omit<
|
||||
FlatRole,
|
||||
'id' | 'workspaceId' | 'uniqueIdentifier' | 'standardId'
|
||||
> & {
|
||||
standardId: string;
|
||||
};
|
||||
+2
@@ -21,6 +21,7 @@ import { WorkspaceSyncFieldMetadataService } from 'src/engine/workspace-manager/
|
||||
import { WorkspaceSyncIndexMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-index-metadata.service';
|
||||
import { WorkspaceSyncObjectMetadataIdentifiersService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata-identifiers.service';
|
||||
import { WorkspaceSyncObjectMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata.service';
|
||||
import { WorkspaceSyncRoleService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-role.service';
|
||||
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
|
||||
|
||||
@Module({
|
||||
@@ -46,6 +47,7 @@ import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/works
|
||||
WorkspaceSyncFieldMetadataRelationService,
|
||||
WorkspaceSyncMetadataService,
|
||||
WorkspaceSyncIndexMetadataService,
|
||||
WorkspaceSyncRoleService,
|
||||
SyncWorkspaceLoggerService,
|
||||
SyncWorkspaceMetadataCommand,
|
||||
],
|
||||
|
||||
+13
@@ -16,6 +16,7 @@ import { WorkspaceSyncFieldMetadataService } from 'src/engine/workspace-manager/
|
||||
import { WorkspaceSyncIndexMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-index-metadata.service';
|
||||
import { WorkspaceSyncObjectMetadataIdentifiersService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata-identifiers.service';
|
||||
import { WorkspaceSyncObjectMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-object-metadata.service';
|
||||
import { WorkspaceSyncRoleService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-role.service';
|
||||
import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage';
|
||||
|
||||
interface SynchronizeOptions {
|
||||
@@ -36,6 +37,7 @@ export class WorkspaceSyncMetadataService {
|
||||
private readonly workspaceSyncIndexMetadataService: WorkspaceSyncIndexMetadataService,
|
||||
private readonly workspaceSyncObjectMetadataIdentifiersService: WorkspaceSyncObjectMetadataIdentifiersService,
|
||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||
private readonly workspaceSyncRoleService: WorkspaceSyncRoleService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -159,6 +161,17 @@ export class WorkspaceSyncMetadataService {
|
||||
`Workspace object metadata identifiers took ${workspaceObjectMetadataIdentifiersEnd - workspaceObjectMetadataIdentifiersStart}ms`,
|
||||
);
|
||||
|
||||
// 6 - Sync standard roles
|
||||
const workspaceRoleMigrationsStart = performance.now();
|
||||
|
||||
await this.workspaceSyncRoleService.synchronize(context, manager);
|
||||
|
||||
const workspaceRoleMigrationsEnd = performance.now();
|
||||
|
||||
this.logger.log(
|
||||
`Workspace role migrations took ${workspaceRoleMigrationsEnd - workspaceRoleMigrationsStart}ms`,
|
||||
);
|
||||
|
||||
const workspaceMigrationsSaveStart = performance.now();
|
||||
|
||||
// Save workspace migrations into the database
|
||||
|
||||
Reference in New Issue
Block a user