Feat: role applicability controls (#14239)
Closes [#1404](https://github.com/twentyhq/core-team-issues/issues/1404) --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
+76
-63
@@ -1,11 +1,12 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import { IsNull, Not, type EntityManager, type Repository } from 'typeorm';
|
||||
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 { AgentRoleService } from 'src/engine/metadata-modules/agent-role/agent-role.service';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
|
||||
import { transformAgentEntityToFlatAgent } from 'src/engine/metadata-modules/flat-agent/utils/transform-agent-entity-to-flat-agent.util';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
@@ -19,7 +20,6 @@ import { WorkspaceAgentComparator } from 'src/engine/workspace-manager/workspace
|
||||
import { StandardAgentFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-agent.factory';
|
||||
import { standardAgentDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents';
|
||||
import { WORKFLOW_CREATION_AGENT } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-agents/agents/workflow-creation-agent';
|
||||
import { ADMIN_ROLE } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/roles/admin-role';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceSyncAgentService {
|
||||
@@ -28,6 +28,7 @@ export class WorkspaceSyncAgentService {
|
||||
constructor(
|
||||
private readonly standardAgentFactory: StandardAgentFactory,
|
||||
private readonly workspaceAgentComparator: WorkspaceAgentComparator,
|
||||
private readonly agentRoleService: AgentRoleService,
|
||||
) {}
|
||||
|
||||
async synchronize(
|
||||
@@ -37,8 +38,6 @@ export class WorkspaceSyncAgentService {
|
||||
this.logger.log('Syncing standard agent.');
|
||||
|
||||
const agentRepository = manager.getRepository(AgentEntity);
|
||||
const roleRepository = manager.getRepository(RoleEntity);
|
||||
const roleTargetsRepository = manager.getRepository(RoleTargetsEntity);
|
||||
|
||||
const existingStandardAgentEntities = await agentRepository.find({
|
||||
where: {
|
||||
@@ -75,13 +74,43 @@ export class WorkspaceSyncAgentService {
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
|
||||
await this.assignAdminRoleToAgent(
|
||||
createdAgent.id,
|
||||
context.workspaceId,
|
||||
roleRepository,
|
||||
roleTargetsRepository,
|
||||
const agentDefinition = standardAgentDefinitions.find(
|
||||
(def) => def.standardId === createdAgent.standardId,
|
||||
);
|
||||
|
||||
if (agentDefinition?.standardRoleId) {
|
||||
try {
|
||||
const roleRepository = manager.getRepository(RoleEntity);
|
||||
const role = await roleRepository.findOne({
|
||||
where: {
|
||||
standardId: agentDefinition.standardRoleId,
|
||||
workspaceId: context.workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
throw new Error(
|
||||
`Standard role with standard ID ${agentDefinition.standardRoleId} not found in workspace`,
|
||||
);
|
||||
}
|
||||
|
||||
const roleTargetsRepository =
|
||||
manager.getRepository(RoleTargetsEntity);
|
||||
|
||||
await roleTargetsRepository.save({
|
||||
roleId: role.id,
|
||||
agentId: createdAgent.id,
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to assign standard role ${agentDefinition.standardRoleId} to agent ${createdAgent.id}: ${error.message}`,
|
||||
error.stack,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (createdAgent.standardId === WORKFLOW_CREATION_AGENT.standardId) {
|
||||
await this.createAgentHandoffToWorkflowCreationAgent(
|
||||
createdAgent.id,
|
||||
@@ -99,9 +128,47 @@ export class WorkspaceSyncAgentService {
|
||||
'id',
|
||||
'universalIdentifier',
|
||||
'workspaceId',
|
||||
'standardRoleId' as keyof typeof agentToUpdate,
|
||||
]);
|
||||
|
||||
await agentRepository.update({ id: agentToUpdate.id }, flatAgentData);
|
||||
|
||||
const agentDefinition = standardAgentDefinitions.find(
|
||||
(def) => def.standardId === agentToUpdate.standardId,
|
||||
);
|
||||
|
||||
if (agentDefinition?.standardRoleId) {
|
||||
try {
|
||||
const roleRepository = manager.getRepository(RoleEntity);
|
||||
const role = await roleRepository.findOne({
|
||||
where: {
|
||||
standardId: agentDefinition.standardRoleId,
|
||||
workspaceId: context.workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!role) {
|
||||
throw new Error(
|
||||
`Standard role with standard ID ${agentDefinition.standardRoleId} not found in workspace`,
|
||||
);
|
||||
}
|
||||
|
||||
const roleTargetsRepository =
|
||||
manager.getRepository(RoleTargetsEntity);
|
||||
|
||||
await roleTargetsRepository.save({
|
||||
roleId: role.id,
|
||||
agentId: agentToUpdate.id,
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to assign standard role ${agentDefinition.standardRoleId} to agent ${agentToUpdate.id}: ${error.message}`,
|
||||
error.stack,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -115,60 +182,6 @@ export class WorkspaceSyncAgentService {
|
||||
}
|
||||
}
|
||||
|
||||
private async assignAdminRoleToAgent(
|
||||
agentId: string,
|
||||
workspaceId: string,
|
||||
roleRepository: Repository<RoleEntity>,
|
||||
roleTargetsRepository: Repository<RoleTargetsEntity>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
const adminRole = await roleRepository.findOne({
|
||||
where: {
|
||||
workspaceId,
|
||||
standardId: ADMIN_ROLE.standardId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!adminRole) {
|
||||
this.logger.warn(
|
||||
`Admin role not found for workspace ${workspaceId}, cannot assign to agent ${agentId}.`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const existingRoleTarget = await roleTargetsRepository.findOne({
|
||||
where: {
|
||||
agentId,
|
||||
roleId: adminRole.id,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingRoleTarget) {
|
||||
this.logger.log(
|
||||
`Workflow creation agent already has admin role assigned`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await roleTargetsRepository.save({
|
||||
roleId: adminRole.id,
|
||||
agentId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Successfully assigned admin role to workflow creation agent`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to assign admin role to workflow creation agent: ${error.message}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async createAgentHandoffToWorkflowCreationAgent(
|
||||
workflowCreationAgentId: string,
|
||||
workspaceId: string,
|
||||
|
||||
+57
-2
@@ -1,12 +1,14 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import { IsNull, Not, type EntityManager } from 'typeorm';
|
||||
import { IsNull, Not, type EntityManager, type Repository } 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 { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
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';
|
||||
@@ -28,12 +30,15 @@ export class WorkspaceSyncRoleService {
|
||||
this.logger.log('Syncing standard role metadata');
|
||||
|
||||
const roleRepository = manager.getRepository(RoleEntity);
|
||||
const permissionFlagRepository =
|
||||
manager.getRepository(PermissionFlagEntity);
|
||||
|
||||
const existingStandardRoleEntities = await roleRepository.find({
|
||||
where: {
|
||||
workspaceId: context.workspaceId,
|
||||
standardId: Not(IsNull()),
|
||||
},
|
||||
relations: ['permissionFlags'],
|
||||
});
|
||||
|
||||
const targetStandardRoles = this.standardRoleFactory.create(
|
||||
@@ -57,10 +62,23 @@ export class WorkspaceSyncRoleService {
|
||||
'id',
|
||||
]);
|
||||
|
||||
await roleRepository.save({
|
||||
const createdRole = await roleRepository.save({
|
||||
...flatRoleData,
|
||||
workspaceId: context.workspaceId,
|
||||
});
|
||||
|
||||
const roleDefinition = standardRoleDefinitions.find(
|
||||
(def) => def.standardId === roleToCreate.standardId,
|
||||
);
|
||||
|
||||
if (roleDefinition?.permissionFlags?.length) {
|
||||
await this.syncPermissionFlags(
|
||||
permissionFlagRepository,
|
||||
createdRole.id,
|
||||
context.workspaceId,
|
||||
roleDefinition.permissionFlags,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -74,6 +92,19 @@ export class WorkspaceSyncRoleService {
|
||||
]);
|
||||
|
||||
await roleRepository.update({ id: roleToUpdate.id }, flatRoleData);
|
||||
|
||||
const roleDefinition = standardRoleDefinitions.find(
|
||||
(def) => def.standardId === roleToUpdate.standardId,
|
||||
);
|
||||
|
||||
if (roleDefinition?.permissionFlags) {
|
||||
await this.syncPermissionFlags(
|
||||
permissionFlagRepository,
|
||||
roleToUpdate.id,
|
||||
context.workspaceId,
|
||||
roleDefinition.permissionFlags,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -86,4 +117,28 @@ export class WorkspaceSyncRoleService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async syncPermissionFlags(
|
||||
permissionFlagRepository: Repository<PermissionFlagEntity>,
|
||||
roleId: string,
|
||||
workspaceId: string,
|
||||
permissionFlags: PermissionFlagType[],
|
||||
): Promise<void> {
|
||||
await permissionFlagRepository.delete({
|
||||
roleId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (permissionFlags.length > 0) {
|
||||
const newPermissionFlags = permissionFlags.map((flag) =>
|
||||
permissionFlagRepository.create({
|
||||
roleId,
|
||||
workspaceId,
|
||||
flag,
|
||||
}),
|
||||
);
|
||||
|
||||
await permissionFlagRepository.save(newPermissionFlags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user