Files
twenty/packages/twenty-server/src/engine/metadata-modules/role/role.resolver.ts
T
Charles Bochet b76dac2ca1 BREAKING CHANGE: Fix graphql errors (#12775)
We were using a global ValidationPipe in main.ts. This is an issue as
@Controllers should return HttpExecption and @Resolvers should return
GraphqlErrors

Removing the global pipe and creating a ResolverValidationPipe able to
generate GraphqlError. We also need to handle the exception in a filter
to avoid nest to think it's unhandled and make it flow to logs


Next step:
- it would be nice to have both @UsePipes(ResolverValidationPipe) +
@UseFilters(GraphqlValidationExceptionFilter) come together. This should
be possible if we create a @GraphQLResolver annotation
2025-06-23 11:23:16 +02:00

221 lines
8.3 KiB
TypeScript

import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
import {
Args,
Mutation,
Parent,
Query,
ResolveField,
Resolver,
} from '@nestjs/graphql';
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 { FileService } from 'src/engine/core-modules/file/services/file.service';
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { SettingsPermissionsGuard } from 'src/engine/guards/settings-permissions.guard';
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
import { UpsertObjectPermissionsInput } from 'src/engine/metadata-modules/object-permission/dtos/upsert-object-permissions.input';
import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service';
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 { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
import { CreateRoleInput } from 'src/engine/metadata-modules/role/dtos/create-role-input.dto';
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
import { UpdateRoleInput } from 'src/engine/metadata-modules/role/dtos/update-role-input.dto';
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
import { SettingPermissionDTO } from 'src/engine/metadata-modules/setting-permission/dtos/setting-permission.dto';
import { UpsertSettingPermissionsInput } from 'src/engine/metadata-modules/setting-permission/dtos/upsert-setting-permission-input';
import { SettingPermissionService } from 'src/engine/metadata-modules/setting-permission/setting-permission.service';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@Resolver(() => RoleDTO)
@UsePipes(ResolverValidationPipe)
@UseGuards(
WorkspaceAuthGuard,
SettingsPermissionsGuard(SettingPermissionType.ROLES),
)
@UseFilters(
PermissionsGraphqlApiExceptionFilter,
PreventNestToAutoLogGraphqlErrorsFilter,
)
export class RoleResolver {
constructor(
private readonly userRoleService: UserRoleService,
private readonly roleService: RoleService,
private readonly userWorkspaceService: UserWorkspaceService,
private readonly featureFlagService: FeatureFlagService,
private readonly objectPermissionService: ObjectPermissionService,
private readonly settingPermissionService: SettingPermissionService,
private readonly fileService: FileService,
) {}
@Query(() => [RoleDTO])
async getRoles(@AuthWorkspace() workspace: Workspace): Promise<RoleDTO[]> {
return this.roleService.getWorkspaceRoles(workspace.id);
}
@Mutation(() => WorkspaceMember)
@UseGuards(UserAuthGuard)
async updateWorkspaceMemberRole(
@AuthWorkspace() workspace: Workspace,
@Args('workspaceMemberId') workspaceMemberId: string,
@Args('roleId', { type: () => String }) roleId: string,
@AuthWorkspaceMemberId()
updatorWorkspaceMemberId: string,
): Promise<WorkspaceMember> {
if (updatorWorkspaceMemberId === workspaceMemberId) {
throw new PermissionsException(
PermissionsExceptionMessage.CANNOT_UPDATE_SELF_ROLE,
PermissionsExceptionCode.CANNOT_UPDATE_SELF_ROLE,
);
}
const workspaceMember =
await this.userWorkspaceService.getWorkspaceMemberOrThrow({
workspaceMemberId,
workspaceId: workspace.id,
});
const userWorkspace =
await this.userWorkspaceService.getUserWorkspaceForUserOrThrow({
userId: workspaceMember.userId,
workspaceId: workspace.id,
});
await this.userRoleService.assignRoleToUserWorkspace({
userWorkspaceId: userWorkspace.id,
workspaceId: workspace.id,
roleId,
});
const roles = await this.userRoleService
.getRolesByUserWorkspaces({
userWorkspaceIds: [userWorkspace.id],
workspaceId: workspace.id,
})
.then(
(rolesByUserWorkspaces) =>
rolesByUserWorkspaces?.get(userWorkspace.id) ?? [],
);
return {
...workspaceMember,
userWorkspaceId: userWorkspace.id,
roles,
} as WorkspaceMember;
}
@Mutation(() => RoleDTO)
async createOneRole(
@AuthWorkspace() workspace: Workspace,
@Args('createRoleInput') createRoleInput: CreateRoleInput,
): Promise<RoleDTO> {
await this.validatePermissionsV2EnabledOrThrow(workspace);
return await this.roleService.createRole({
workspaceId: workspace.id,
input: createRoleInput,
});
}
@Mutation(() => RoleDTO)
async updateOneRole(
@AuthWorkspace() workspace: Workspace,
@Args('updateRoleInput') updateRoleInput: UpdateRoleInput,
): Promise<RoleDTO> {
await this.validatePermissionsV2EnabledOrThrow(workspace);
const role = await this.roleService.updateRole({
input: updateRoleInput,
workspaceId: workspace.id,
});
return role;
}
@Mutation(() => String)
async deleteOneRole(
@AuthWorkspace() workspace: Workspace,
@Args('roleId') roleId: string,
): Promise<string> {
await this.validatePermissionsV2EnabledOrThrow(workspace);
const deletedRoleId = await this.roleService.deleteRole(
roleId,
workspace.id,
);
return deletedRoleId;
}
@Mutation(() => [ObjectPermissionDTO])
async upsertObjectPermissions(
@AuthWorkspace() workspace: Workspace,
@Args('upsertObjectPermissionsInput')
upsertObjectPermissionsInput: UpsertObjectPermissionsInput,
): Promise<ObjectPermissionDTO[]> {
await this.validatePermissionsV2EnabledOrThrow(workspace);
return this.objectPermissionService.upsertObjectPermissions({
workspaceId: workspace.id,
input: upsertObjectPermissionsInput,
});
}
@Mutation(() => [SettingPermissionDTO])
async upsertSettingPermissions(
@AuthWorkspace() workspace: Workspace,
@Args('upsertSettingPermissionsInput')
upsertSettingPermissionsInput: UpsertSettingPermissionsInput,
): Promise<SettingPermissionDTO[]> {
await this.validatePermissionsV2EnabledOrThrow(workspace);
return this.settingPermissionService.upsertSettingPermissions({
workspaceId: workspace.id,
input: upsertSettingPermissionsInput,
});
}
@ResolveField('workspaceMembers', () => [WorkspaceMember])
async getWorkspaceMembersAssignedToRole(
@Parent() role: RoleDTO,
@AuthWorkspace() workspace: Workspace,
): Promise<WorkspaceMemberWorkspaceEntity[]> {
const workspaceMembers =
await this.userRoleService.getWorkspaceMembersAssignedToRole(
role.id,
workspace.id,
);
return workspaceMembers;
}
private async validatePermissionsV2EnabledOrThrow(workspace: Workspace) {
const isPermissionsV2Enabled =
await this.featureFlagService.isFeatureEnabled(
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
workspace.id,
);
if (!isPermissionsV2Enabled) {
throw new PermissionsException(
PermissionsExceptionMessage.PERMISSIONS_V2_NOT_ENABLED,
PermissionsExceptionCode.PERMISSIONS_V2_NOT_ENABLED,
);
}
}
}