Skip to content

Commit

Permalink
Merge pull request #91 from code0-tech/assign-abilities-to-roles
Browse files Browse the repository at this point in the history
Add mutation to assign abilities to roles
  • Loading branch information
Taucher2003 authored Mar 3, 2024
2 parents d70a99f + 48f9496 commit b77d506
Show file tree
Hide file tree
Showing 16 changed files with 335 additions and 4 deletions.
28 changes: 28 additions & 0 deletions app/graphql/mutations/team_roles/assign_abilities.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

module Mutations
module TeamRoles
class AssignAbilities < BaseMutation
description 'Update the abilities a role is granted.'

field :abilities, [Types::TeamRoleAbilityEnum], description: 'The now granted abilities'

argument :abilities, [Types::TeamRoleAbilityEnum],
description: 'The abilities that should be granted to the ability'
argument :role_id, Types::GlobalIdType[::TeamRole],
description: 'The id of the role which should be granted the abilities'

def resolve(role_id:, abilities:)
role = SagittariusSchema.object_from_id(role_id)

return { abilities: nil, errors: [create_message_error('Invalid role')] } if role.nil?

::TeamRoles::AssignAbilitiesService.new(
current_user,
role,
abilities
).execute.to_mutation_response(success_key: :abilities)
end
end
end
end
5 changes: 5 additions & 0 deletions app/graphql/types/base_enum.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@
module Types
class BaseEnum < GraphQL::Schema::Enum
include Sagittarius::Graphql::HasMarkdownDocumentation

def self.inherited(subclass)
super
subclass.graphql_name subclass.name.delete_prefix('Types::').delete_suffix('Enum').gsub('::', '')
end
end
end
1 change: 1 addition & 0 deletions app/graphql/types/mutation_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class MutationType < Types::BaseObject
mount_mutation Mutations::ApplicationSettings::Update
mount_mutation Mutations::TeamMembers::AssignRoles
mount_mutation Mutations::TeamMembers::Invite
mount_mutation Mutations::TeamRoles::AssignAbilities
mount_mutation Mutations::TeamRoles::Create
mount_mutation Mutations::Teams::Create
mount_mutation Mutations::Users::Login
Expand Down
11 changes: 11 additions & 0 deletions app/graphql/types/team_role_ability_enum.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module Types
class TeamRoleAbilityEnum < BaseEnum
description 'Represents abilities that can be granted to roles in teams.'

TeamRoleAbility::ABILITIES.each do |ability, settings|
value ability.upcase, settings[:description], value: ability, **settings.except(:db, :description)
end
end
end
5 changes: 5 additions & 0 deletions app/graphql/types/team_role_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@ class TeamRoleType < BaseObject

authorize :read_team_role

field :abilities, [Types::TeamRoleAbilityEnum], null: false, description: 'The abilities the role is granted'
field :name, String, null: false, description: 'The name of this role'
field :team, Types::TeamType, null: false, description: 'The team where this role belongs to'

id_field ::TeamRole
timestamps

def abilities
object.abilities.map(&:ability)
end
end
end
1 change: 1 addition & 0 deletions app/models/audit_event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class AuditEvent < ApplicationRecord
team_role_created: 5,
team_member_invited: 6,
team_member_roles_updated: 7,
team_role_abilities_updated: 8,
}.with_indifferent_access

enum :action_type, ACTION_TYPES, prefix: :action
Expand Down
9 changes: 5 additions & 4 deletions app/models/team_role_ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

class TeamRoleAbility < ApplicationRecord
ABILITIES = {
create_team_role: 1,
invite_member: 2,
assign_member_roles: 3,
create_team_role: { db: 1, description: 'Allows the creation of roles in a team' },
invite_member: { db: 2, description: 'Allows to invite new members to a team' },
assign_member_roles: { db: 3, description: 'Allows to change the roles of a team member' },
assign_role_abilities: { db: 4, description: 'Allows to change the abilities of a team role' },
}.with_indifferent_access

enum :ability, ABILITIES, prefix: :can
enum :ability, ABILITIES.transform_values { |v| v[:db] }, prefix: :can

belongs_to :team_role, inverse_of: :abilities

Expand Down
1 change: 1 addition & 0 deletions app/policies/team_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ class TeamPolicy < BasePolicy
customizable_permission :create_team_role
customizable_permission :invite_member
customizable_permission :assign_member_roles
customizable_permission :assign_role_abilities
end
55 changes: 55 additions & 0 deletions app/services/team_roles/assign_abilities_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

module TeamRoles
class AssignAbilitiesService
include Sagittarius::Database::Transactional

attr_reader :current_user, :role, :abilities

def initialize(current_user, role, abilities)
@current_user = current_user
@role = role
@abilities = abilities
end

def execute
team = role.team
unless Ability.allowed?(current_user, :assign_role_abilities, team)
return ServiceResponse.error(message: 'Missing permissions', payload: :missing_permission)
end

transactional do |t|
current_abilities = role.abilities
old_abilities_for_audit_event = current_abilities.map(&:ability)

current_abilities.where.not(ability: abilities).delete_all

(abilities - current_abilities.map(&:ability)).map do |ability|
team_role_ability = TeamRoleAbility.create(team_role: role, ability: ability)

next if team_role_ability.persisted?

t.rollback_and_return! ServiceResponse.error(
message: 'Failed to save team role ability',
payload: team_role_ability.errors
)
end

new_abilities = role.reload.abilities.map(&:ability)

AuditService.audit(
:team_role_abilities_updated,
author_id: current_user.id,
entity: role,
details: {
old_abilities: old_abilities_for_audit_event,
new_abilities: new_abilities,
},
target: team
)

ServiceResponse.success(message: 'Role abilities updated', payload: new_abilities)
end
end
end
end
12 changes: 12 additions & 0 deletions docs/content/graphql/enum/teamroleability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
title: TeamRoleAbility
---

Represents abilities that can be granted to roles in teams.

| Value | Description |
|-------|-------------|
| `ASSIGN_MEMBER_ROLES` | Allows to change the roles of a team member |
| `ASSIGN_ROLE_ABILITIES` | Allows to change the abilities of a team role |
| `CREATE_TEAM_ROLE` | Allows the creation of roles in a team |
| `INVITE_MEMBER` | Allows to invite new members to a team |
21 changes: 21 additions & 0 deletions docs/content/graphql/mutation/teamrolesassignabilities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: teamRolesAssignAbilities
---

Update the abilities a role is granted.

## Arguments

| Name | Type | Description |
|------|------|-------------|
| `abilities` | [`[TeamRoleAbility!]!`](../enum/teamroleability.md) | The abilities that should be granted to the ability |
| `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. |
| `roleId` | [`TeamRoleID!`](../scalar/teamroleid.md) | The id of the role which should be granted the abilities |

## Fields

| Name | Type | Description |
|------|------|-------------|
| `abilities` | [`[TeamRoleAbility!]`](../enum/teamroleability.md) | The now granted abilities |
| `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. |
| `errors` | [`[Error!]!`](../union/error.md) | Errors encountered during execution of the mutation. |
1 change: 1 addition & 0 deletions docs/content/graphql/object/teamrole.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Represents a team role.

| Name | Type | Description |
|------|------|-------------|
| `abilities` | [`[TeamRoleAbility!]!`](../enum/teamroleability.md) | The abilities the role is granted |
| `createdAt` | [`Time!`](../scalar/time.md) | Time when this TeamRole was created |
| `id` | [`TeamRoleID!`](../scalar/teamroleid.md) | Global ID of this TeamRole |
| `name` | [`String!`](../scalar/string.md) | The name of this role |
Expand Down
7 changes: 7 additions & 0 deletions spec/graphql/mutations/team_roles/assign_abilities_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe Mutations::TeamRoles::AssignAbilities do
it { expect(described_class.graphql_name).to eq('TeamRolesAssignAbilities') }
end
1 change: 1 addition & 0 deletions spec/graphql/types/team_role_type_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
id
team
name
abilities
createdAt
updatedAt
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe 'teamRolesAssignAbilities Mutation' do
include GraphqlHelpers

subject(:mutate!) { post_graphql mutation, variables: variables, current_user: current_user }

let(:mutation) do
<<~QUERY
mutation($input: TeamRolesAssignAbilitiesInput!) {
teamRolesAssignAbilities(input: $input) {
#{error_query}
abilities
}
}
QUERY
end

let(:team_role) { create(:team_role) }
let(:input) do
{
roleId: team_role.to_global_id.to_s,
abilities: ['CREATE_TEAM_ROLE'],
}
end

let(:variables) { { input: input } }
let(:current_user) { create(:user) }

context 'when user has permission' do
before do
stub_allowed_ability(TeamPolicy, :assign_role_abilities, user: current_user, subject: team_role.team)
end

it 'assigns the given abilities to the role' do
mutate!

abilities = graphql_data_at(:team_roles_assign_abilities, :abilities)
expect(abilities).to be_present
expect(abilities).to be_a(Array)

expect(abilities).to eq(['CREATE_TEAM_ROLE'])

is_expected.to create_audit_event(
:team_role_abilities_updated,
author_id: current_user.id,
entity_id: team_role.id,
entity_type: 'TeamRole',
details: {
'new_abilities' => ['create_team_role'],
'old_abilities' => [],
},
target_id: team_role.team.id,
target_type: 'Team'
)
end
end

context 'when user does not have permission' do
it 'returns an error' do
mutate!

expect(graphql_data_at(:team_roles_assign_abilities, :abilities)).to be_nil
expect(graphql_data_at(:team_roles_assign_abilities, :errors)).to include({ 'message' => 'missing_permission' })
end
end
end
Loading

0 comments on commit b77d506

Please sign in to comment.