Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement role creation #41

Merged
merged 1 commit into from
Jan 7, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions app/graphql/mutations/team_roles/create.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

module Mutations
module TeamRoles
class Create < BaseMutation
description 'Create a new role in a team.'

field :team_role, Types::TeamRoleType, description: 'The newly created team role'

argument :name, String, description: 'The name for the new role'
argument :team_id, Types::GlobalIdType[::Team], description: 'The id of the team which this role will belong to'

def resolve(team_id:, **params)
team = SagittariusSchema.object_from_id(team_id)

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

::TeamRoles::CreateService.new(current_user, team, params).execute.to_mutation_response(success_key: :team_role)
end
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
@@ -7,6 +7,7 @@ class MutationType < Types::BaseObject
include Sagittarius::Graphql::MountMutation

mount_mutation Mutations::ApplicationSettings::Update
mount_mutation Mutations::TeamRoles::Create
mount_mutation Mutations::Teams::Create
mount_mutation Mutations::Users::Login
mount_mutation Mutations::Users::Logout
15 changes: 15 additions & 0 deletions app/graphql/types/team_role_type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

module Types
class TeamRoleType < BaseObject
description 'Represents a team role.'

authorize :read_team_role

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
end
end
1 change: 1 addition & 0 deletions app/models/audit_event.rb
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ class AuditEvent < ApplicationRecord
user_logged_in: 2,
team_created: 3,
application_setting_updated: 4,
team_role_created: 5,
}.with_indifferent_access

enum :action_type, ACTION_TYPES, prefix: :action
2 changes: 2 additions & 0 deletions app/models/team.rb
Original file line number Diff line number Diff line change
@@ -9,6 +9,8 @@ class Team < ApplicationRecord
has_many :team_members, inverse_of: :team
has_many :users, through: :team_members, inverse_of: :teams

has_many :roles, class_name: 'TeamRole', inverse_of: :team

def member?(user)
return false if user.nil?

10 changes: 10 additions & 0 deletions app/models/team_role.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

class TeamRole < ApplicationRecord
belongs_to :team, inverse_of: :roles

validates :name, presence: true,
length: { minimum: 3, maximum: 50 },
allow_blank: false,
uniqueness: { case_sensitive: false, scope: :team_id }
end
2 changes: 2 additions & 0 deletions app/policies/team_policy.rb
Original file line number Diff line number Diff line change
@@ -6,5 +6,7 @@ class TeamPolicy < BasePolicy
rule { is_member }.policy do
enable :read_team
enable :read_team_member
enable :create_team_role
enable :read_team_role
end
end
5 changes: 5 additions & 0 deletions app/policies/team_role_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# frozen_string_literal: true

class TeamRolePolicy < BasePolicy
delegate { @subject.team }
end
39 changes: 39 additions & 0 deletions app/services/team_roles/create_service.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# frozen_string_literal: true

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

attr_reader :current_user, :team, :params

def initialize(current_user, team, params)
@current_user = current_user
@team = team
@params = params
end

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

transactional do
team_role = TeamRole.create(team: team, **params)

unless team_role.persisted?
return ServiceResponse.error(message: 'Failed to save team role', payload: team_role.errors)
end

AuditService.audit(
:team_role_created,
author_id: current_user.id,
entity: team_role,
details: { name: params[:name] },
target: team
)

ServiceResponse.success(message: 'Team role created', payload: team_role)
end
end
end
end
14 changes: 14 additions & 0 deletions db/migrate/20240105213134_create_team_roles.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: true

class CreateTeamRoles < Sagittarius::Database::Migration[1.0]
def change
create_table :team_roles do |t|
t.references :team, null: false, foreign_key: true
t.text :name, null: false

t.index '"team_id", LOWER("name")', unique: true

t.timestamps_with_timezone
end
end
end
1 change: 1 addition & 0 deletions db/schema_migrations/20240105213134
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cf4f81ad02f8203b10cd8c703bf545f11e59651685383ecdb39923d199b87f07
29 changes: 29 additions & 0 deletions db/structure.sql
Original file line number Diff line number Diff line change
@@ -66,6 +66,23 @@ CREATE SEQUENCE team_members_id_seq

ALTER SEQUENCE team_members_id_seq OWNED BY team_members.id;

CREATE TABLE team_roles (
id bigint NOT NULL,
team_id bigint NOT NULL,
name text NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);

CREATE SEQUENCE team_roles_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;

ALTER SEQUENCE team_roles_id_seq OWNED BY team_roles.id;

CREATE TABLE teams (
id bigint NOT NULL,
name text NOT NULL,
@@ -132,6 +149,8 @@ ALTER TABLE ONLY audit_events ALTER COLUMN id SET DEFAULT nextval('audit_events_

ALTER TABLE ONLY team_members ALTER COLUMN id SET DEFAULT nextval('team_members_id_seq'::regclass);

ALTER TABLE ONLY team_roles ALTER COLUMN id SET DEFAULT nextval('team_roles_id_seq'::regclass);

ALTER TABLE ONLY teams ALTER COLUMN id SET DEFAULT nextval('teams_id_seq'::regclass);

ALTER TABLE ONLY user_sessions ALTER COLUMN id SET DEFAULT nextval('user_sessions_id_seq'::regclass);
@@ -153,6 +172,9 @@ ALTER TABLE ONLY schema_migrations
ALTER TABLE ONLY team_members
ADD CONSTRAINT team_members_pkey PRIMARY KEY (id);

ALTER TABLE ONLY team_roles
ADD CONSTRAINT team_roles_pkey PRIMARY KEY (id);

ALTER TABLE ONLY teams
ADD CONSTRAINT teams_pkey PRIMARY KEY (id);

@@ -172,6 +194,10 @@ CREATE UNIQUE INDEX index_team_members_on_team_id_and_user_id ON team_members US

CREATE INDEX index_team_members_on_user_id ON team_members USING btree (user_id);

CREATE INDEX index_team_roles_on_team_id ON team_roles USING btree (team_id);

CREATE UNIQUE INDEX "index_team_roles_on_team_id_LOWER_name" ON team_roles USING btree (team_id, lower(name));

CREATE UNIQUE INDEX "index_teams_on_LOWER_name" ON teams USING btree (lower(name));

CREATE UNIQUE INDEX index_user_sessions_on_token ON user_sessions USING btree (token);
@@ -191,5 +217,8 @@ ALTER TABLE ONLY team_members
ALTER TABLE ONLY user_sessions
ADD CONSTRAINT fk_rails_9fa262d742 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;

ALTER TABLE ONLY team_roles
ADD CONSTRAINT fk_rails_af974e1e44 FOREIGN KEY (team_id) REFERENCES teams(id);

ALTER TABLE ONLY audit_events
ADD CONSTRAINT fk_rails_f64374fc56 FOREIGN KEY (author_id) REFERENCES users(id);
21 changes: 21 additions & 0 deletions docs/content/graphql/mutation/teamrolescreate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
title: teamRolesCreate
---

Create a new role in a team.

## Arguments

| Name | Type | Description |
|------|------|-------------|
| `clientMutationId` | [`String`](../scalar/string.md) | A unique identifier for the client performing the mutation. |
| `name` | [`String!`](../scalar/string.md) | The name for the new role |
| `teamId` | [`TeamID!`](../scalar/teamid.md) | The id of the team which this role will belong to |

## Fields

| Name | Type | Description |
|------|------|-------------|
| `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. |
| `teamRole` | [`TeamRole`](../object/teamrole.md) | The newly created team role |
16 changes: 16 additions & 0 deletions docs/content/graphql/object/teamrole.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: TeamRole
---

Represents a team role.

## Fields without arguments

| Name | Type | Description |
|------|------|-------------|
| `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 |
| `team` | [`Team!`](../object/team.md) | The team where this role belongs to |
| `updatedAt` | [`Time!`](../scalar/time.md) | Time when this TeamRole was last updated |

5 changes: 5 additions & 0 deletions docs/content/graphql/scalar/teamroleid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: TeamRoleID
---

A unique identifier for all TeamRole entities of the application
10 changes: 10 additions & 0 deletions spec/factories/team_roles.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

FactoryBot.define do
sequence(:role_name) { |n| "role#{n}" }

factory :team_role do
team
name { generate(:role_name) }
end
end
7 changes: 7 additions & 0 deletions spec/graphql/mutations/team_roles/create_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::Create do
it { expect(described_class.graphql_name).to eq('TeamRolesCreate') }
end
19 changes: 19 additions & 0 deletions spec/graphql/types/team_role_type_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe SagittariusSchema.types['TeamRole'] do
let(:fields) do
%w[
id
team
name
createdAt
updatedAt
]
end

it { expect(described_class.graphql_name).to eq('TeamRole') }
it { expect(described_class).to have_graphql_fields(fields) }
it { expect(described_class).to require_graphql_authorizations(:read_team_role) }
end
17 changes: 17 additions & 0 deletions spec/models/team_role_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

require 'rails_helper'

RSpec.describe TeamRole do
subject { create(:team_role) }

describe 'associations' do
it { is_expected.to belong_to(:team).required }
end

describe 'validations' do
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:name).case_insensitive.scoped_to(:team_id) }
it { is_expected.to validate_length_of(:name).is_at_most(50) }
end
end
Loading