Code first roles sync (#13667)
This commit is contained in:
@@ -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>;
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+23
@@ -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"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+19
@@ -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;
|
||||
};
|
||||
+21
@@ -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,
|
||||
};
|
||||
};
|
||||
+16
@@ -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,
|
||||
|
||||
+1
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+15
-2
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+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