Code first roles sync (#13667)

This commit is contained in:
Abdul Rahman
2025-08-11 16:44:45 +05:30
committed by GitHub
parent 2a2a2faadf
commit 9b11735c57
26 changed files with 474 additions and 73 deletions
@@ -2845,6 +2845,7 @@ export type Role = {
label: Scalars['String'];
objectPermissions?: Maybe<Array<ObjectPermission>>;
permissionFlags?: Maybe<Array<PermissionFlag>>;
standardId?: Maybe<Scalars['UUID']>;
workspaceMembers: Array<WorkspaceMember>;
};
@@ -2691,6 +2691,7 @@ export type Role = {
label: Scalars['String'];
objectPermissions?: Maybe<Array<ObjectPermission>>;
permissionFlags?: Maybe<Array<PermissionFlag>>;
standardId?: Maybe<Scalars['UUID']>;
workspaceMembers: Array<WorkspaceMember>;
};
@@ -12,12 +12,12 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants';
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { WorkspaceFeatureFlagsMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.service';
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { ADMIN_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role';
@Command({
name: 'upgrade:1-3:assign-roles-to-existing-api-keys',
@@ -217,7 +217,7 @@ export class AssignRolesToExistingApiKeysCommand extends ActiveOrSuspendedWorksp
const adminRole = await this.roleRepository.findOne({
where: {
workspaceId,
label: ADMIN_ROLE_LABEL,
standardId: ADMIN_ROLE.standardId,
},
});
@@ -0,0 +1,23 @@
import { type MigrationInterface, type QueryRunner } from 'typeorm';
import { ADMIN_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role';
export class AddStandardIdToRole1754480368129 implements MigrationInterface {
name = 'AddStandardIdToRole1754480368129';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "core"."role" ADD "standardId" uuid`);
await queryRunner.query(
`UPDATE "core"."role" SET "standardId" = '${ADMIN_ROLE.standardId}' WHERE "label" = 'Admin'`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`UPDATE "core"."role" SET "standardId" = NULL`);
await queryRunner.query(
`ALTER TABLE "core"."role" DROP COLUMN "standardId"`,
);
}
}
@@ -0,0 +1,19 @@
import { type MigrationInterface, type QueryRunner } from 'typeorm';
export class UpdateMemberRoleCanAccessAllTools1753318977614
implements MigrationInterface
{
name = 'UpdateMemberRoleCanAccessAllTools1753318977614';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`UPDATE "core"."role" SET "canAccessAllTools" = true WHERE "label" = 'Member'`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`UPDATE "core"."role" SET "canAccessAllTools" = false WHERE "label" = 'Member'`,
);
}
}
@@ -11,9 +11,9 @@ import { wrapJsonRpcResponse } from 'src/engine/core-modules/ai/utils/wrap-jsonr
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 { type Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { ADMIN_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role';
@Injectable()
export class McpService {
@@ -60,7 +60,7 @@ export class McpService {
const roles = await this.roleRepository.find({
where: {
workspaceId,
label: ADMIN_ROLE_LABEL,
standardId: ADMIN_ROLE.standardId,
},
});
@@ -0,0 +1,18 @@
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
export const roleEntityRelationProperties = [
'roleTargets',
'objectPermissions',
'permissionFlags',
'fieldPermissions',
] as const;
export type RoleEntityRelationProperties =
(typeof roleEntityRelationProperties)[number];
export type FlatRole = Omit<
RoleEntity,
RoleEntityRelationProperties | 'createdAt' | 'updatedAt'
> & {
uniqueIdentifier: string;
};
@@ -0,0 +1,21 @@
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';
export const fromRoleEntityToFlatRole = (role: RoleEntity): FlatRole => {
return {
id: role.id,
standardId: role.standardId,
label: role.label,
description: role.description,
icon: role.icon,
isEditable: role.isEditable,
canUpdateAllSettings: role.canUpdateAllSettings,
canAccessAllTools: role.canAccessAllTools,
canReadAllObjectRecords: role.canReadAllObjectRecords,
canUpdateAllObjectRecords: role.canUpdateAllObjectRecords,
canSoftDeleteAllObjectRecords: role.canSoftDeleteAllObjectRecords,
canDestroyAllObjectRecords: role.canDestroyAllObjectRecords,
workspaceId: role.workspaceId,
uniqueIdentifier: role.standardId || role.id,
};
};
@@ -0,0 +1,16 @@
import { v4 } from 'uuid';
import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type';
import { type StandardRoleDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/types/standard-role-definition.interface';
export const fromStandardRoleDefinitionToFlatRole = (
standardRoleDefinition: StandardRoleDefinition,
workspaceId: string,
): FlatRole => {
return {
...standardRoleDefinition,
id: v4(),
workspaceId,
uniqueIdentifier: standardRoleDefinition.standardId || v4(),
};
};
@@ -14,6 +14,9 @@ export class RoleDTO {
@Field(() => UUIDScalarType, { nullable: false })
id: string;
@Field(() => UUIDScalarType, { nullable: true })
standardId?: string;
@Field({ nullable: false })
label: string;
@@ -20,6 +20,9 @@ export class RoleEntity {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ nullable: true, type: 'uuid' })
standardId?: string;
@Column({ nullable: false })
label: string;
@@ -5,7 +5,6 @@ import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants';
import { MEMBER_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/member-role-label.constants';
import {
PermissionsException,
@@ -145,26 +144,6 @@ export class RoleService {
return { ...existingRole, ...updatedRole };
}
public async createAdminRole({
workspaceId,
}: {
workspaceId: string;
}): Promise<RoleEntity> {
return this.roleRepository.save({
label: ADMIN_ROLE_LABEL,
description: 'Admin role',
icon: 'IconUserCog',
canUpdateAllSettings: true,
canAccessAllTools: true,
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true,
canDestroyAllObjectRecords: true,
isEditable: false,
workspaceId,
});
}
public async deleteRole(
roleId: string,
workspaceId: string,
@@ -226,7 +205,7 @@ export class RoleService {
description: 'Member role',
icon: 'IconUser',
canUpdateAllSettings: false,
canAccessAllTools: false,
canAccessAllTools: true,
canReadAllObjectRecords: true,
canUpdateAllObjectRecords: true,
canSoftDeleteAllObjectRecords: true,
@@ -4,6 +4,7 @@ import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
export const fromRoleEntityToRoleDto = (role: RoleEntity): RoleDTO => {
return {
id: role.id,
standardId: role.standardId,
label: role.label,
canUpdateAllSettings: role.canUpdateAllSettings,
canAccessAllTools: role.canAccessAllTools,
@@ -4,7 +4,6 @@ import { isDefined } from 'twenty-shared/utils';
import { In, Not, Repository } from 'typeorm';
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants';
import {
PermissionsException,
PermissionsExceptionCode,
@@ -14,6 +13,7 @@ import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { ADMIN_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role';
import { type WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
export class UserRoleService {
@@ -202,7 +202,10 @@ export class UserRoleService {
);
}
if (roleOfUserWorkspace.label === ADMIN_ROLE_LABEL) {
if (
isDefined(roleOfUserWorkspace) &&
roleOfUserWorkspace.standardId === ADMIN_ROLE.standardId
) {
const adminRole = roleOfUserWorkspace;
await this.validateMoreThanOneWorkspaceMemberHasAdminRoleOrThrow({
@@ -268,7 +271,12 @@ export class UserRoleService {
};
}
if (!(currentRole?.label === ADMIN_ROLE_LABEL)) {
if (
!(
isDefined(currentRole) &&
currentRole.standardId === ADMIN_ROLE.standardId
)
) {
return;
}
@@ -9,6 +9,7 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-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 { RoleService } from 'src/engine/metadata-modules/role/role.service';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
@@ -18,6 +19,7 @@ import {
SEED_YCOMBINATOR_WORKSPACE_ID,
} from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspaces.util';
import { API_KEY_DATA_SEED_IDS } from 'src/engine/workspace-manager/dev-seeder/data/constants/api-key-data-seeds.constant';
import { ADMIN_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role';
@Injectable()
export class DevSeederPermissionsService {
@@ -29,16 +31,27 @@ export class DevSeederPermissionsService {
private readonly objectPermissionService: ObjectPermissionService,
@InjectRepository(ObjectMetadataEntity, 'core')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
@InjectRepository(RoleEntity, 'core')
private readonly roleRepository: Repository<RoleEntity>,
private readonly typeORMService: TypeORMService,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly fieldPermissionService: FieldPermissionService,
) {}
public async initPermissions(workspaceId: string) {
const adminRole = await this.roleService.createAdminRole({
workspaceId,
const adminRole = await this.roleRepository.findOne({
where: {
standardId: ADMIN_ROLE.standardId,
workspaceId,
},
});
if (!adminRole) {
throw new Error(
'Required roles not found. Make sure the permission sync has run.',
);
}
const dataSource = this.typeORMService.getMainDataSource();
if (dataSource) {
@@ -20,6 +20,7 @@ import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { prefillCoreViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-core-views';
import { standardObjectsPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/standard-objects-prefill-data';
import { ADMIN_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role';
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
@Injectable()
@@ -89,15 +90,7 @@ export class WorkspaceManagerService {
`Metadata creation took ${dataSourceMetadataCreationEnd - dataSourceMetadataCreationStart}ms`,
);
const permissionsEnabledStart = performance.now();
await this.initPermissions({ workspaceId, userId });
const permissionsEnabledEnd = performance.now();
this.logger.log(
`Permissions enabled took ${permissionsEnabledEnd - permissionsEnabledStart}ms`,
);
await this.setupDefaultRoles(workspaceId, userId);
if (featureFlags[FeatureFlagKey.IS_AI_ENABLED]) {
const defaultAgentEnabledStart = performance.now();
@@ -189,39 +182,6 @@ export class WorkspaceManagerService {
this.logger.log(`workspace ${workspaceId} schema deleted`);
}
private async initPermissions({
workspaceId,
userId,
}: {
workspaceId: string;
userId: string;
}) {
const adminRole = await this.roleService.createAdminRole({
workspaceId,
});
const userWorkspace = await this.userWorkspaceRepository.findOneOrFail({
where: {
workspaceId,
userId,
},
});
await this.userRoleService.assignRoleToUserWorkspace({
workspaceId,
userWorkspaceId: userWorkspace.id,
roleId: adminRole.id,
});
const memberRole = await this.roleService.createMemberRole({
workspaceId,
});
await this.workspaceRepository.update(workspaceId, {
defaultRoleId: memberRole.id,
});
}
private async initDefaultAgent(workspaceId: string) {
const agent = await this.agentService.createOneAgent(
{
@@ -239,4 +199,36 @@ export class WorkspaceManagerService {
defaultAgentId: agent.id,
});
}
private async setupDefaultRoles(
workspaceId: string,
userId: string,
): Promise<void> {
const adminRole = await this.roleRepository.findOne({
where: {
standardId: ADMIN_ROLE.standardId,
workspaceId,
},
});
if (adminRole) {
const userWorkspace = await this.userWorkspaceRepository.findOneOrFail({
where: { workspaceId, userId },
});
await this.userRoleService.assignRoleToUserWorkspace({
workspaceId,
userWorkspaceId: userWorkspace.id,
roleId: adminRole.id,
});
}
const memberRole = await this.roleService.createMemberRole({
workspaceId,
});
await this.workspaceRepository.update(workspaceId, {
defaultRoleId: memberRole.id,
});
}
}
@@ -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,
];
@@ -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;
}
}
@@ -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,
];
@@ -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;
}
}
@@ -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;
}
}
}
}
}
@@ -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[];
@@ -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,
};
@@ -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;
};
@@ -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,
],
@@ -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