192 lines
6.9 KiB
Ruby
192 lines
6.9 KiB
Ruby
class Api::V1::Accounts::InternalChat::MessagesController < Api::V1::Accounts::InternalChat::BaseController
|
|
include Events::Types
|
|
|
|
before_action :current_channel
|
|
before_action :fetch_message, only: [:update, :destroy, :pin, :unpin, :thread]
|
|
|
|
MESSAGES_PER_PAGE = 50
|
|
|
|
def index
|
|
authorize current_channel, :show?, policy_class: InternalChat::ChannelPolicy
|
|
@messages = paginated_messages
|
|
render json: {
|
|
messages: @messages.map { |msg| message_response(msg) },
|
|
meta: pagination_meta
|
|
}
|
|
end
|
|
|
|
def create
|
|
authorize current_channel, :show?, policy_class: InternalChat::ChannelPolicy
|
|
@message = InternalChat::MessageCreateService.new(
|
|
channel: current_channel,
|
|
sender: Current.user,
|
|
params: message_params
|
|
).perform
|
|
render json: message_response(@message), status: :created
|
|
end
|
|
|
|
def update
|
|
authorize @message, :update?, policy_class: InternalChat::MessagePolicy
|
|
previous_content = @message.content
|
|
@message.update!(
|
|
content: update_params[:content],
|
|
content_attributes: (@message.content_attributes || {}).merge('edited_at' => Time.current.iso8601, 'previous_content' => previous_content)
|
|
)
|
|
dispatch_message_event(INTERNAL_CHAT_MESSAGE_UPDATED, message: @message)
|
|
render json: message_response(@message)
|
|
end
|
|
|
|
def destroy
|
|
authorize @message, :destroy?, policy_class: InternalChat::MessagePolicy
|
|
message_data = {
|
|
id: @message.id,
|
|
internal_chat_channel_id: @message.internal_chat_channel_id,
|
|
account_id: @message.account_id
|
|
}
|
|
@message.update!(content: I18n.t('internal_chat.messages.deleted'), content_attributes: { deleted: true })
|
|
dispatch_message_event(INTERNAL_CHAT_MESSAGE_DELETED, message_data: message_data)
|
|
head :ok
|
|
end
|
|
|
|
def pin
|
|
authorize @message, :pin?, policy_class: InternalChat::MessagePolicy
|
|
@message.skip_content_validation = true
|
|
@message.update!(content_attributes: (@message.content_attributes || {}).merge('pinned' => true, 'pinned_by' => Current.user.id,
|
|
'pinned_at' => Time.current.iso8601))
|
|
dispatch_message_event(INTERNAL_CHAT_MESSAGE_UPDATED, message: @message)
|
|
render json: message_response(@message)
|
|
end
|
|
|
|
def unpin
|
|
authorize @message, :unpin?, policy_class: InternalChat::MessagePolicy
|
|
@message.skip_content_validation = true
|
|
attrs = (@message.content_attributes || {}).except('pinned', 'pinned_by', 'pinned_at')
|
|
@message.update!(content_attributes: attrs)
|
|
dispatch_message_event(INTERNAL_CHAT_MESSAGE_UPDATED, message: @message)
|
|
render json: message_response(@message)
|
|
end
|
|
|
|
def thread
|
|
authorize @message, :thread?, policy_class: InternalChat::MessagePolicy
|
|
replies = @message.replies.includes(:sender, :reactions, :replies, :attachments, :poll).ordered
|
|
render json: {
|
|
parent: message_response(@message),
|
|
replies: replies.map { |msg| message_response(msg) }
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def fetch_message
|
|
@message = current_channel.messages.find(params[:id])
|
|
end
|
|
|
|
def paginated_messages
|
|
return fetch_around_messages if params[:around].present?
|
|
|
|
messages = apply_time_filters(base_messages_scope)
|
|
if params[:after].present?
|
|
messages.ordered.limit(MESSAGES_PER_PAGE)
|
|
else
|
|
messages.ordered.last(MESSAGES_PER_PAGE)
|
|
end
|
|
rescue ArgumentError
|
|
base_messages_scope.ordered.last(MESSAGES_PER_PAGE)
|
|
end
|
|
|
|
def fetch_around_messages
|
|
target = current_channel.messages.find_by(id: params[:around])
|
|
return base_messages_scope.ordered.last(MESSAGES_PER_PAGE) unless target
|
|
|
|
half = MESSAGES_PER_PAGE / 2
|
|
before_msgs = base_messages_scope.where('internal_chat_messages.created_at <= ?', target.created_at)
|
|
.ordered.last(half)
|
|
after_msgs = base_messages_scope.where('internal_chat_messages.created_at > ?', target.created_at)
|
|
.ordered.limit(half)
|
|
(before_msgs + after_msgs).uniq(&:id).sort_by(&:created_at)
|
|
end
|
|
|
|
def base_messages_scope
|
|
current_channel.messages
|
|
.includes(:sender, :reactions, :replies, :attachments, :poll)
|
|
.where("parent_id IS NULL OR (content_attributes->>'also_send_in_channel')::boolean = true")
|
|
end
|
|
|
|
def apply_time_filters(messages)
|
|
messages = messages.where('internal_chat_messages.created_at < ?', Time.zone.parse(params[:before])) if params[:before].present?
|
|
messages = messages.where('internal_chat_messages.created_at > ?', Time.zone.parse(params[:after])) if params[:after].present?
|
|
messages
|
|
end
|
|
|
|
def pagination_meta
|
|
{
|
|
has_more: @messages.size >= MESSAGES_PER_PAGE
|
|
}
|
|
end
|
|
|
|
def message_params
|
|
params.permit(:content, :content_type, :parent_id, :echo_id, :also_send_in_channel, attachments: [:file, :file_type])
|
|
end
|
|
|
|
def update_params
|
|
params.permit(:content)
|
|
end
|
|
|
|
def message_response(message) # rubocop:disable Metrics/AbcSize
|
|
deleted = message.content_attributes&.dig('deleted')
|
|
response = {
|
|
id: message.id,
|
|
content: message.content,
|
|
content_type: message.content_type,
|
|
content_attributes: message.content_attributes,
|
|
internal_chat_channel_id: message.internal_chat_channel_id,
|
|
sender: message.sender&.push_event_data,
|
|
parent_id: message.parent_id,
|
|
echo_id: message.echo_id,
|
|
replies_count: message.replies_count,
|
|
created_at: message.created_at,
|
|
updated_at: message.updated_at,
|
|
reactions: message.reactions.includes(:user).map { |r| { id: r.id, emoji: r.emoji, user_id: r.user_id, user: { name: r.user&.name } } },
|
|
attachments: deleted ? [] : message.attachments.map { |a| attachment_response(a) }
|
|
}
|
|
response[:poll] = poll_data(message.poll) if !deleted && message.poll?
|
|
response
|
|
end
|
|
|
|
def poll_data(poll)
|
|
return nil unless poll
|
|
|
|
{
|
|
id: poll.id,
|
|
question: poll.question,
|
|
multiple_choice: poll.multiple_choice,
|
|
public_results: poll.public_results,
|
|
allow_revote: poll.allow_revote,
|
|
expires_at: poll.expires_at,
|
|
options: poll.options.ordered.includes(votes: :user).map { |o| poll_option_data(o, poll) },
|
|
total_votes: poll.total_votes_count
|
|
}
|
|
end
|
|
|
|
def poll_option_data(option, poll)
|
|
data = { id: option.id, text: option.text, emoji: option.emoji, votes_count: option.votes_count,
|
|
voted: option.votes.any? { |v| v.user_id == Current.user.id } }
|
|
data[:voters] = option.votes.map { |v| v.user.push_event_data } if poll.public_results
|
|
data
|
|
end
|
|
|
|
def attachment_response(attachment)
|
|
{
|
|
id: attachment.id,
|
|
file_type: attachment.file_type,
|
|
external_url: attachment.external_url,
|
|
extension: attachment.extension,
|
|
file_url: attachment.file.attached? ? url_for(attachment.file) : nil
|
|
}
|
|
end
|
|
|
|
def dispatch_message_event(event, data)
|
|
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, **data)
|
|
end
|
|
end
|