import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; import { DataSource, In, Repository } from 'typeorm'; import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants'; import { PermissionsException, PermissionsExceptionCode, PermissionsExceptionMessage, } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity'; import { UpsertSettingPermissionsInput } from 'src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input'; import { SettingPermissionEntity } from 'src/engine/metadata-modules/setting-permission/setting-permission.entity'; export class SettingPermissionService { constructor( @InjectRepository(SettingPermissionEntity, 'metadata') private readonly settingPermissionRepository: Repository, @InjectRepository(RoleEntity, 'metadata') private readonly roleRepository: Repository, @InjectDataSource('metadata') private readonly metadataDataSource: DataSource, ) {} public async upsertSettingPermissions({ workspaceId, input, }: { workspaceId: string; input: UpsertSettingPermissionsInput; }): Promise { await this.validateRoleIsEditableOrThrow({ roleId: input.roleId, workspaceId, }); const invalidSettings = input.settingPermissionKeys.filter( (setting) => !Object.values(SettingPermissionType).includes(setting), ); if (invalidSettings.length > 0) { throw new PermissionsException( `${PermissionsExceptionMessage.INVALID_SETTING}: ${invalidSettings.join(', ')}`, PermissionsExceptionCode.INVALID_SETTING, ); } const queryRunner = this.metadataDataSource.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { const existingPermissions = await queryRunner.manager.find( SettingPermissionEntity, { where: { roleId: input.roleId, workspaceId, }, }, ); const existingSettings = new Set( existingPermissions.map((p) => p.setting), ); const inputSettings = new Set(input.settingPermissionKeys); const settingsToAdd = input.settingPermissionKeys.filter( (setting) => !existingSettings.has(setting), ); const permissionsToRemove = existingPermissions.filter( (permission) => !inputSettings.has(permission.setting), ); if (permissionsToRemove.length > 0) { await queryRunner.manager.delete(SettingPermissionEntity, { id: In(permissionsToRemove.map((p) => p.id)), }); } if (settingsToAdd.length > 0) { const newPermissions = settingsToAdd.map((setting) => queryRunner.manager.create(SettingPermissionEntity, { workspaceId, roleId: input.roleId, setting, }), ); await queryRunner.manager.save(SettingPermissionEntity, newPermissions); } await queryRunner.commitTransaction(); return queryRunner.manager.find(SettingPermissionEntity, { where: { roleId: input.roleId, workspaceId }, order: { setting: 'ASC' }, }); } catch (error) { await queryRunner.rollbackTransaction(); if (error.message.includes('violates foreign key constraint')) { const role = await this.roleRepository.findOne({ where: { id: input.roleId }, }); if (!isDefined(role)) { throw new PermissionsException( PermissionsExceptionMessage.ROLE_NOT_FOUND, PermissionsExceptionCode.ROLE_NOT_FOUND, ); } } throw error; } finally { await queryRunner.release(); } } private async validateRoleIsEditableOrThrow({ roleId, workspaceId, }: { roleId: string; workspaceId: string; }) { const role = await this.roleRepository.findOne({ where: { id: roleId, workspaceId, }, }); if (!role?.isEditable) { throw new PermissionsException( PermissionsExceptionMessage.ROLE_NOT_EDITABLE, PermissionsExceptionCode.ROLE_NOT_EDITABLE, ); } } }