Skip to content
Merged
Show file tree
Hide file tree
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
60 changes: 60 additions & 0 deletions backend/app/controllers/api/v1/chat_channels_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
module Api
module V1
class ChatChannelsController < BaseController
before_action :set_organization, only: [ :index, :create ]
before_action :set_chat_channel, only: [ :update, :destroy ]

def index
authorize ChatChannel
@chat_channels = policy_scope(@organization.chat_channels)
.includes(:organization, :chat_conversation)
.order(:name)
end

def create
chat_channel = @organization.chat_channels.new(channel_params.merge(created_by: current_user))
authorize chat_channel

@chat_channel = ::Chats::ChannelCreator.call(
organization: @organization,
created_by: current_user,
attributes: channel_params
)
render :show, status: :created
rescue ActiveRecord::RecordInvalid => e
render_errors(e.record)
end

def update
authorize @chat_channel

if @chat_channel.update(channel_params)
render :show
else
render_errors(@chat_channel)
end
end

def destroy
authorize @chat_channel
@chat_channel.chat_conversation.destroy!

head :no_content
end

private

def set_organization
@organization = policy_scope(Organization).find(params[:organization_id])
end

def set_chat_channel
@chat_channel = policy_scope(ChatChannel).find(params[:id])
end

def channel_params
params.require(:chat_channel).permit(:name, :description)
end
end
end
end
28 changes: 28 additions & 0 deletions backend/app/controllers/api/v1/chats/conversations_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module Api
module V1
module Chats
class ConversationsController < BaseController
before_action :set_conversation, only: [ :show ]

def index
@conversations = policy_scope(ChatConversation)
.where(kind: %w[ direct channel ])
.includes(:organization, :participants, :chat_channel, parent_message: :author)
.order(Arel.sql("last_message_at DESC NULLS LAST"), created_at: :desc)
end

def show
authorize @conversation
end

private

def set_conversation
@conversation = policy_scope(ChatConversation)
.includes(:organization, :participants, :chat_channel, parent_message: :author)
.find(params[:id])
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module Api
module V1
module Chats
class DirectConversationsController < BaseController
def create
other_user = policy_scope(User).find(params.require(:user_id))
@conversation = ::Chats::DirectConversationFinder.call(
current_user:,
other_user:
)
authorize @conversation, :show?
render "api/v1/chats/conversations/show", status: :created
rescue ActiveRecord::RecordInvalid => e
skip_authorization
render_errors(e.record)
end
end
end
end
end
58 changes: 58 additions & 0 deletions backend/app/controllers/api/v1/chats/messages_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
module Api
module V1
module Chats
class MessagesController < BaseController
before_action :set_conversation, only: [ :index, :create ]
before_action :set_message, only: [ :thread ]

def index
authorize @conversation, :show?
scoped_messages = policy_scope(@conversation.chat_messages)
.includes(:author, thread_conversation: :chat_messages)
.order(id: :desc)
.limit(limit_param)
scoped_messages = scoped_messages.where("chat_messages.id < ?", params[:before_id]) if params[:before_id].present?

@messages = scoped_messages.to_a.reverse
end

def create
authorize @conversation, :create_message?

@message = @conversation.chat_messages.new(message_params.merge(author: current_user))
if @message.save
render :show, status: :created
else
render_errors(@message)
end
end

def thread
authorize @message, :create_thread?

existing_thread = @message.thread_conversation
@conversation = ::Chats::ThreadCreator.call(parent_message: @message, created_by: current_user)
render "api/v1/chats/conversations/show", status: existing_thread ? :ok : :created
end

private

def set_conversation
@conversation = policy_scope(ChatConversation).find(params[:conversation_id])
end

def set_message
@message = policy_scope(ChatMessage).find(params[:id])
end

def message_params
params.require(:message).permit(:body)
end

def limit_param
params.fetch(:limit, 50).to_i.clamp(1, 100)
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,65 +1,65 @@
module Api
module V1
class GroupsController < BaseController
before_action :set_group, only: [ :show, :update, :destroy ]
class OrganizationsController < BaseController
before_action :set_organization, only: [ :show, :update, :destroy ]

def index
@groups = policy_scope(Group).includes(:parent_group, :group_memberships, :users).order(:name)
@organizations = policy_scope(Organization).includes(:organization_memberships, :users).order(:name)
end

def show
authorize @group
authorize @organization
end

def create
group = Group.new
organization = Organization.new

persist_group(group, :created)
persist_organization(organization, :created)
end

def update
persist_group(@group)
persist_organization(@organization)
end

def destroy
authorize @group
@group.destroy!
authorize @organization
@organization.destroy!

head :no_content
end

private

def set_group
@group = policy_scope(Group).find(params[:id])
def set_organization
@organization = policy_scope(Organization).find(params[:id])
end

def persist_group(group, status = :ok)
attributes = group_params
def persist_organization(organization, status = :ok)
attributes = organization_params
user_ids = normalize_user_ids(attributes.delete(:user_ids))
admin_user_ids = normalize_user_ids(attributes.delete(:admin_user_ids))
user_ids = include_current_user(user_ids) if group.new_record?
admin_user_ids = include_current_user(admin_user_ids) if group.new_record?
user_ids = include_current_user(user_ids) if organization.new_record?
admin_user_ids = include_current_user(admin_user_ids) if organization.new_record?

group.assign_attributes(attributes)
authorize group
organization.assign_attributes(attributes)
authorize organization
return render_invalid_user_ids(:user_ids) unless valid_user_ids?(user_ids)
return render_invalid_user_ids(:admin_user_ids) unless valid_user_ids?(admin_user_ids)

Group.transaction do
group.save!
sync_group_memberships(group, user_ids, admin_user_ids)
ensure_group_has_admin!(group)
Organization.transaction do
organization.save!
sync_organization_memberships(organization, user_ids, admin_user_ids)
ensure_organization_has_admin!(organization)
end

@group = group.reload
@organization = organization.reload
render :show, status:
rescue ActiveRecord::RecordInvalid
render_errors(group)
render_errors(organization)
end

def group_params
params.require(:group).permit(:name, :description, :parent_group_id, user_ids: [], admin_user_ids: [])
def organization_params
params.require(:organization).permit(:name, :description, user_ids: [], admin_user_ids: [])
end

def normalize_user_ids(raw_user_ids)
Expand Down Expand Up @@ -89,27 +89,27 @@ def include_current_user(user_ids)
(user_ids + [ current_user.id ]).uniq
end

def sync_group_memberships(group, user_ids, admin_user_ids)
def sync_organization_memberships(organization, user_ids, admin_user_ids)
return if user_ids.nil? && admin_user_ids.nil?

memberships = group.group_memberships.index_by(&:user_id)
memberships = organization.organization_memberships.index_by(&:user_id)
final_user_ids = user_ids || memberships.keys
final_admin_user_ids = admin_user_ids || memberships.values.select(&:admin?).map(&:user_id)
final_user_ids = (final_user_ids + final_admin_user_ids).uniq

if final_admin_user_ids.empty?
group.errors.add(:admin_user_ids, :blank)
raise ActiveRecord::RecordInvalid, group
organization.errors.add(:admin_user_ids, :blank)
raise ActiveRecord::RecordInvalid, organization
end

final_admin_user_ids.each do |user_id|
membership = memberships[user_id] || group.group_memberships.build(user_id:)
membership = memberships[user_id] || organization.organization_memberships.build(user_id:)
membership.admin = true
membership.save!
end

(final_user_ids - final_admin_user_ids).each do |user_id|
membership = memberships[user_id] || group.group_memberships.build(user_id:)
membership = memberships[user_id] || organization.organization_memberships.build(user_id:)
membership.admin = false
membership.save!
end
Expand All @@ -119,11 +119,11 @@ def sync_group_memberships(group, user_ids, admin_user_ids)
end
end

def ensure_group_has_admin!(group)
return if group.group_memberships.where(admin: true).exists?
def ensure_organization_has_admin!(organization)
return if organization.organization_memberships.where(admin: true).exists?

group.errors.add(:admin_user_ids, :blank)
raise ActiveRecord::RecordInvalid, group
organization.errors.add(:admin_user_ids, :blank)
raise ActiveRecord::RecordInvalid, organization
end

def render_invalid_user_ids(attribute)
Expand Down
6 changes: 3 additions & 3 deletions backend/app/controllers/api/v1/projects_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class ProjectsController < BaseController
before_action :set_project, only: [ :show, :update, :destroy, :board ]

def index
@projects = policy_scope(Project).includes(:group).order(created_at: :desc)
@projects = policy_scope(Project).includes(:owner).order(created_at: :desc)
end

def show
Expand Down Expand Up @@ -53,11 +53,11 @@ def board
private

def set_project
@project = policy_scope(Project).includes(:group).find(params[:id])
@project = policy_scope(Project).includes(:owner).find(params[:id])
end

def project_params
params.require(:project).permit(:name, :description, :group_id)
params.require(:project).permit(:name, :description, :owner_type, :owner_id)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion backend/app/controllers/api/v1/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ class UsersController < BaseController

def index
@users = policy_scope(User)
.includes(:groups)
.includes(:organizations)
.order(:email)
.page(page_param)
.per(per_page_param)
Expand Down
23 changes: 23 additions & 0 deletions backend/app/models/chat_channel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
class ChatChannel < ApplicationRecord
belongs_to :organization
belongs_to :chat_conversation
belongs_to :created_by, class_name: "User"

validates :name, presence: true, uniqueness: { scope: :organization_id, case_sensitive: false }
validates :chat_conversation_id, uniqueness: true
validate :conversation_must_be_channel

before_validation :normalize_name

private

def normalize_name
self.name = name.to_s.strip
end

def conversation_must_be_channel
return if chat_conversation.blank? || chat_conversation.channel?

errors.add(:chat_conversation, "must be a channel conversation")
end
end
Loading
Loading