Skip to content

Commit

Permalink
feat: add managing role system for chats
Browse files Browse the repository at this point in the history
  • Loading branch information
kkkiikkk committed Sep 19, 2024
1 parent 937adb4 commit 066c4fa
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 31 deletions.
58 changes: 44 additions & 14 deletions app/controllers/chat_members_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ class ChatMembersController < ApplicationController
before_action :authenticate_user!
before_action :set_workspace
before_action :set_chat
before_action :set_chat_member, only: [:destroy, :index, :new, :create]
before_action :require_admin_or_owner!, only: [:new, :create]
before_action :set_chat_member
before_action :require_permissions, only: [:new, :create, :edit, :update]

Check failure on line 6 in app/controllers/chat_members_controller.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

Check failure on line 6 in app/controllers/chat_members_controller.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.
before_action :require_owner!, only: [:edit, :update]

Check failure on line 7 in app/controllers/chat_members_controller.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

Check failure on line 7 in app/controllers/chat_members_controller.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

def index
@chat_members = @chat.chat_members
Expand All @@ -16,26 +17,33 @@ def new
def create
@chat_member = @chat.chat_members.new(chat_member_params)
@chat_member.role = 'member'
@chat_member.chat = @chat
if @chat_member.save
redirect_to workspace_path, notice: 'User was succesfully added to the chat'
redirect_to workspace_chats_path(@workspace), notice: 'User was successfully added to the chat'
else
render :new, notice: 'Failed creation. Try again'
end
render :new, alert: 'Failed creation. Try again'
end
end

def destroy
@chat_member.destroy
redirect_to workspaces_path, notice: 'User were succesfully removed from the chat'
result = ChatMembersService::Destroy.call(target_chat_member(params[:id]), @chat_member)
handle_result(result)
end

def edit
@target_chat_member = target_chat_member(params[:id])
end

def update
update_params = update_chat_member_params
result = ChatMembersService::ChangeRole.call(target_chat_member(params[:id]), @chat_member, update_params[:role])
handle_result(result)
end

private

def set_workspace
@workspace = current_user.workspaces.find_by(id: params[:workspace_id])
unless @workspace
redirect_to workspaces_path, notice: 'Workspace not found or not accessible.'
end
redirect_to workspaces_path, notice: 'Workspace not found or not accessible.' unless @workspace
end

def set_chat
Expand All @@ -46,13 +54,35 @@ def set_chat_member
@chat_member = @chat.chat_members.find_by(user_id: current_user.id)
end

def require_admin_or_owner!
if ChatMemberPolicy.new(current_user, @chat_member).member?
redirect_to workspaces_path, notice: 'You can not manage chat members because you have no access'
def target_chat_member(id)
@target_chat_member ||= @chat.chat_members.find_by(id: id) # Memoization to prevent redundant queries
end

def require_permissions
if !ChatMemberPolicy.new(current_user, @chat_member).can_manage?
redirect_to workspaces_path, alert: 'You do not have access to manage chat members'
end
end

def require_owner!
unless ChatMemberPolicy.new(current_user, @chat_member).owner?
redirect_to workspaces_path, alert: 'Only owners can edit or update members'
end
end

def chat_member_params
params.require(:chat_member).permit(:user_id)
end

def update_chat_member_params
params.require(:chat_member).permit(:role)
end

def handle_result(result)
if result.success?
redirect_to workspace_chats_path(@workspace), notice: result[:payload][:text]
else
redirect_to workspace_chats_path(@workspace), alert: result[:error]
end
end
end
22 changes: 18 additions & 4 deletions app/controllers/chats_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,20 @@ def index
end

def show
@chat = @workspace.chats.find(params[:id])
@chat = @workspace.chats.find_by(id: params[:id])

if @chat.nil?
redirect_to workspace_chats_path(@workspace), notice: 'Chat not found.'
return
end

return if @chat.chat_type == 'public'

unless @chat.chat_members.exists?(user_id: current_user.id)
redirect_to workspace_chats_path(@workspace), notice: 'You cannot access this chat as you are not a member.'
end
end


def new
@chat = @workspace.chats.new
Expand All @@ -23,9 +35,7 @@ def create
@chat = @workspace.chats.new(chat_params)
render :new
end
end


end

def destroy
@chat = @workspace.chats.find(params[:id])
Expand All @@ -52,4 +62,8 @@ def require_workspace_owner!
redirect_to workspaces_path, notice: 'You can not manage the chat because you are not workspace owner'
end
end

def chat_member
@chat.chat_members.find_by(user_id: current_user[:id])
end
end
9 changes: 0 additions & 9 deletions app/models/chat_member.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,4 @@ class ChatMember < ApplicationRecord
belongs_to :user

validates :role, inclusion: { in: %w[member owner admin], message: "%{value} is not a valid role" }
validate :chat_must_be_private_or_p2p

private

def chat_must_be_private_or_p2p
unless chat.chat_type.in?(%w[private p2p])
errors.add(:chat, 'must be private or p2p')
end
end
end
4 changes: 4 additions & 0 deletions app/policies/chat_member_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ def can_remove?
def can_add_members?
['owner', 'admin'].include?(record.role)
end

def owner_can_remove?
!record_owner?
end
end
29 changes: 29 additions & 0 deletions app/services/chat_members_service/change_role.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module ChatMembersService
class ChangeRole < ApplicationService
def call(target_chat_member, chat_member, role)
return failure('You cannot grant the role of owner to another user') if role == 'owner'

if can_change_role?(chat_member, target_chat_member)
update_role(target_chat_member, role)
else
failure('You cannot change user permissions')
end
end

private

def can_change_role?(chat_member, target_chat_member)
ChatMemberPolicy.new(chat_member.user, chat_member).owner? &&
target_chat_member.user != chat_member.user
end

def update_role(target_chat_member, role)
target_chat_member.role = role
if target_chat_member.save
success({ text: 'User role was successfully updated' })
else
failure('Error while updating user role')
end
end
end
end
29 changes: 29 additions & 0 deletions app/services/chat_members_service/destroy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module ChatMembersService
class Destroy < ApplicationService
def call(target_chat_member, chat_member)
if can_destroy?(chat_member, target_chat_member)
target_chat_member.destroy
success({ text: destroy_message(chat_member, target_chat_member) })
else
failure('You cannot delete this member')
end
end

private

def can_destroy?(chat_member, target_chat_member)
policy = ChatMemberPolicy.new(chat_member.user, chat_member)
(policy.owner? && target_chat_member != chat_member) ||
(policy.admin? && target_chat_member.role != 'admin') ||
(policy.member? && target_chat_member == chat_member)
end

def destroy_message(chat_member, target_chat_member)
if chat_member == target_chat_member
"You've left the chat"
else
"You've deleted the user from the chat"
end
end
end
end
14 changes: 14 additions & 0 deletions app/views/chat_members/edit.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<h1>Edit Role for <%= @target_chat_member.user.name %></h1>

<%= form_for [@workspace, @chat, @target_chat_member], url: workspace_chat_chat_member_path(@workspace, @chat, @target_chat_member), method: :patch do |f| %>
<div class="form-group">
<%= f.label :role %>
<%= f.select :role, options_for_select([['Member', 'member'], ['Admin', 'admin']], @target_chat_member.role), {}, class: 'form-control' %>
</div>

<div class="actions">
<%= f.submit 'Update Role', class: 'btn btn-primary' %>
</div>
<% end %>

<%= link_to 'Back to Members List', workspace_chat_chat_members_path(@workspace, @chat), class: 'btn btn-secondary' %>
7 changes: 4 additions & 3 deletions app/views/chat_members/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
<% @chat_members.each do |member| %>
<li>
<%= member.user.name %> - <%= member.role.capitalize %>
<%= link_to 'Remove', workspace_chat_chat_member_path(@workspace, @chat, member), method: :delete, data: { confirm: 'Are you sure?', turbo_method: :delete }, class: 'btn btn-danger' %>
<%= link_to 'Remove', workspace_chat_chat_member_path(@workspace, @chat, member), method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger' %>
<%= link_to 'Edit Role', edit_workspace_chat_chat_member_path(@workspace, @chat, member), class: 'btn btn-warning' %>
</li>
<% end %>
</ul>

<%= link_to 'Add Member', new_workspace_chat_chat_member_path(@workspace, @chat), class: 'btn btn-primary' %>
<%= link_to 'Add Member', new_workspace_chat_chat_member_path(@workspace, @chat), class: 'btn btn-primary' %>

<%= link_to 'Back to Chat', workspace_chat_path(@workspace, @chat) %>
<%= link_to 'Back to Chat', workspace_chat_path(@workspace, @chat), class: 'btn btn-secondary' %>
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
resource :invites, only: [:create]

resources :chats do
resources :chat_members, only: [:index, :new, :create, :destroy]
resources :chat_members, only: [:index, :new, :create, :destroy, :edit, :update]
end
end

Expand Down

0 comments on commit 066c4fa

Please sign in to comment.