2023-03-17 14:24:38 +01:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
module Chat
|
|
|
|
module Publisher
|
|
|
|
def self.new_messages_message_bus_channel(chat_channel_id)
|
2023-04-24 09:32:04 +10:00
|
|
|
"#{root_message_bus_channel(chat_channel_id)}/new-messages"
|
2023-03-17 14:24:38 +01:00
|
|
|
end
|
|
|
|
|
2023-03-28 14:45:45 +10:00
|
|
|
def self.root_message_bus_channel(chat_channel_id)
|
|
|
|
"/chat/#{chat_channel_id}"
|
|
|
|
end
|
|
|
|
|
2023-04-13 22:45:50 +10:00
|
|
|
def self.thread_message_bus_channel(chat_channel_id, thread_id)
|
|
|
|
"#{root_message_bus_channel(chat_channel_id)}/thread/#{thread_id}"
|
|
|
|
end
|
|
|
|
|
2023-05-05 08:55:55 +02:00
|
|
|
def self.calculate_publish_targets(channel, message, staged_thread_id: nil)
|
2023-04-20 14:38:00 +10:00
|
|
|
return [root_message_bus_channel(channel.id)] if !allow_publish_to_thread?(channel)
|
|
|
|
|
|
|
|
if message.thread_om?
|
|
|
|
[
|
|
|
|
root_message_bus_channel(channel.id),
|
|
|
|
thread_message_bus_channel(channel.id, message.thread_id),
|
|
|
|
]
|
2023-05-05 08:55:55 +02:00
|
|
|
elsif staged_thread_id || message.thread_reply?
|
|
|
|
targets = [thread_message_bus_channel(channel.id, message.thread_id)]
|
|
|
|
targets << thread_message_bus_channel(channel.id, staged_thread_id) if staged_thread_id
|
|
|
|
targets
|
2023-04-20 14:38:00 +10:00
|
|
|
else
|
|
|
|
[root_message_bus_channel(channel.id)]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.allow_publish_to_thread?(channel)
|
|
|
|
SiteSetting.enable_experimental_chat_threaded_discussions && channel.threading_enabled
|
2023-04-13 22:45:50 +10:00
|
|
|
end
|
|
|
|
|
2023-05-05 08:55:55 +02:00
|
|
|
def self.publish_new!(chat_channel, chat_message, staged_id, staged_thread_id: nil)
|
|
|
|
message_bus_targets =
|
|
|
|
calculate_publish_targets(chat_channel, chat_message, staged_thread_id: staged_thread_id)
|
2023-04-24 09:32:04 +10:00
|
|
|
publish_to_targets!(
|
|
|
|
message_bus_targets,
|
|
|
|
chat_channel,
|
2023-05-05 08:55:55 +02:00
|
|
|
serialize_message_with_type(chat_message, :sent).merge(
|
|
|
|
staged_id: staged_id,
|
|
|
|
staged_thread_id: staged_thread_id,
|
|
|
|
),
|
2023-04-24 09:32:04 +10:00
|
|
|
)
|
2023-04-13 22:45:50 +10:00
|
|
|
|
|
|
|
# NOTE: This means that the read count is only updated in the client
|
|
|
|
# for new messages in the main channel stream, maybe in future we want to
|
|
|
|
# do this for thread messages as well?
|
2023-04-20 14:38:00 +10:00
|
|
|
if !chat_message.thread_reply? || !allow_publish_to_thread?(chat_channel)
|
2023-04-13 22:45:50 +10:00
|
|
|
MessageBus.publish(
|
|
|
|
self.new_messages_message_bus_channel(chat_channel.id),
|
|
|
|
{
|
|
|
|
channel_id: chat_channel.id,
|
|
|
|
message_id: chat_message.id,
|
|
|
|
user_id: chat_message.user.id,
|
|
|
|
username: chat_message.user.username,
|
|
|
|
thread_id: chat_message.thread_id,
|
|
|
|
},
|
2023-04-24 09:32:04 +10:00
|
|
|
permissions(chat_channel),
|
2023-04-13 22:45:50 +10:00
|
|
|
)
|
|
|
|
end
|
2023-03-17 14:24:38 +01:00
|
|
|
end
|
|
|
|
|
2023-04-24 09:32:04 +10:00
|
|
|
def self.publish_thread_original_message_metadata!(thread)
|
|
|
|
publish_to_channel!(
|
|
|
|
thread.channel,
|
|
|
|
{
|
|
|
|
type: :update_thread_original_message,
|
|
|
|
original_message_id: thread.original_message_id,
|
|
|
|
replies_count: thread.replies_count_cache,
|
2023-05-10 11:42:32 +02:00
|
|
|
title: thread.title,
|
2023-04-24 09:32:04 +10:00
|
|
|
},
|
|
|
|
)
|
|
|
|
end
|
2023-04-20 15:09:47 -05:00
|
|
|
|
2023-05-05 08:55:55 +02:00
|
|
|
def self.publish_thread_created!(chat_channel, chat_message, thread_id, staged_thread_id)
|
|
|
|
publish_to_channel!(
|
|
|
|
chat_channel,
|
|
|
|
serialize_message_with_type(
|
|
|
|
chat_message,
|
|
|
|
:thread_created,
|
|
|
|
{ thread_id: thread_id, staged_thread_id: staged_thread_id },
|
|
|
|
),
|
|
|
|
)
|
2023-04-12 11:09:06 +10:00
|
|
|
end
|
|
|
|
|
2023-03-17 14:24:38 +01:00
|
|
|
def self.publish_processed!(chat_message)
|
2023-04-16 10:30:33 +02:00
|
|
|
chat_channel = chat_message.chat_channel
|
2023-04-13 22:45:50 +10:00
|
|
|
message_bus_targets = calculate_publish_targets(chat_channel, chat_message)
|
2023-04-24 09:32:04 +10:00
|
|
|
publish_to_targets!(
|
|
|
|
message_bus_targets,
|
|
|
|
chat_channel,
|
|
|
|
{ type: :processed, chat_message: { id: chat_message.id, cooked: chat_message.cooked } },
|
|
|
|
)
|
2023-03-17 14:24:38 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_edit!(chat_channel, chat_message)
|
2023-04-13 22:45:50 +10:00
|
|
|
message_bus_targets = calculate_publish_targets(chat_channel, chat_message)
|
2023-04-24 09:32:04 +10:00
|
|
|
publish_to_targets!(
|
|
|
|
message_bus_targets,
|
|
|
|
chat_channel,
|
|
|
|
serialize_message_with_type(chat_message, :edit),
|
|
|
|
)
|
2023-03-17 14:24:38 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_refresh!(chat_channel, chat_message)
|
2023-04-13 22:45:50 +10:00
|
|
|
message_bus_targets = calculate_publish_targets(chat_channel, chat_message)
|
2023-04-24 09:32:04 +10:00
|
|
|
publish_to_targets!(
|
|
|
|
message_bus_targets,
|
|
|
|
chat_channel,
|
|
|
|
serialize_message_with_type(chat_message, :refresh),
|
|
|
|
)
|
2023-03-17 14:24:38 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_reaction!(chat_channel, chat_message, action, user, emoji)
|
2023-04-13 22:45:50 +10:00
|
|
|
message_bus_targets = calculate_publish_targets(chat_channel, chat_message)
|
2023-04-24 09:32:04 +10:00
|
|
|
publish_to_targets!(
|
|
|
|
message_bus_targets,
|
|
|
|
chat_channel,
|
|
|
|
{
|
|
|
|
action: action,
|
|
|
|
user: BasicUserSerializer.new(user, root: false).as_json,
|
|
|
|
emoji: emoji,
|
|
|
|
type: :reaction,
|
|
|
|
chat_message_id: chat_message.id,
|
|
|
|
},
|
|
|
|
)
|
2023-03-17 14:24:38 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_presence!(chat_channel, user, typ)
|
|
|
|
raise NotImplementedError
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_delete!(chat_channel, chat_message)
|
2023-04-13 22:45:50 +10:00
|
|
|
message_bus_targets = calculate_publish_targets(chat_channel, chat_message)
|
2023-04-24 09:32:04 +10:00
|
|
|
publish_to_targets!(
|
|
|
|
message_bus_targets,
|
|
|
|
chat_channel,
|
|
|
|
{ type: "delete", deleted_id: chat_message.id, deleted_at: chat_message.deleted_at },
|
|
|
|
)
|
2023-03-17 14:24:38 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_bulk_delete!(chat_channel, deleted_message_ids)
|
2023-04-24 09:32:04 +10:00
|
|
|
channel_permissions = permissions(chat_channel)
|
2023-04-18 08:28:20 +10:00
|
|
|
Chat::Thread
|
|
|
|
.grouped_messages(message_ids: deleted_message_ids)
|
|
|
|
.each do |group|
|
|
|
|
MessageBus.publish(
|
|
|
|
thread_message_bus_channel(chat_channel.id, group.thread_id),
|
|
|
|
{
|
2023-04-24 09:32:04 +10:00
|
|
|
type: :bulk_delete,
|
2023-04-18 08:28:20 +10:00
|
|
|
deleted_ids: group.thread_message_ids,
|
|
|
|
deleted_at: Time.zone.now,
|
|
|
|
},
|
2023-04-24 09:32:04 +10:00
|
|
|
channel_permissions,
|
2023-04-18 08:28:20 +10:00
|
|
|
)
|
|
|
|
|
|
|
|
# Don't need to publish to the main channel if the messages deleted
|
|
|
|
# were a part of the thread (except the original message ID, since
|
|
|
|
# that shows in the main channel).
|
|
|
|
deleted_message_ids =
|
|
|
|
deleted_message_ids - (group.thread_message_ids - [group.original_message_id])
|
|
|
|
end
|
|
|
|
|
|
|
|
return if deleted_message_ids.empty?
|
|
|
|
|
2023-04-24 09:32:04 +10:00
|
|
|
publish_to_channel!(
|
|
|
|
chat_channel,
|
|
|
|
{ type: :bulk_delete, deleted_ids: deleted_message_ids, deleted_at: Time.zone.now },
|
2023-03-17 14:24:38 +01:00
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_restore!(chat_channel, chat_message)
|
2023-04-13 22:45:50 +10:00
|
|
|
message_bus_targets = calculate_publish_targets(chat_channel, chat_message)
|
2023-04-24 09:32:04 +10:00
|
|
|
publish_to_targets!(
|
|
|
|
message_bus_targets,
|
|
|
|
chat_channel,
|
|
|
|
serialize_message_with_type(chat_message, :restore),
|
|
|
|
)
|
2023-03-17 14:24:38 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_flag!(chat_message, user, reviewable, score)
|
2023-04-13 22:45:50 +10:00
|
|
|
message_bus_targets = calculate_publish_targets(chat_message.chat_channel, chat_message)
|
|
|
|
|
2023-04-24 09:32:04 +10:00
|
|
|
# Publish to user who created flag
|
|
|
|
publish_to_targets!(
|
|
|
|
message_bus_targets,
|
|
|
|
chat_message.chat_channel,
|
|
|
|
{
|
|
|
|
type: :self_flagged,
|
|
|
|
user_flag_status: score.status_for_database,
|
|
|
|
chat_message_id: chat_message.id,
|
|
|
|
},
|
|
|
|
permissions: {
|
2023-04-13 22:45:50 +10:00
|
|
|
user_ids: [user.id],
|
2023-04-24 09:32:04 +10:00
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
# Publish flag with link to reviewable to staff
|
|
|
|
publish_to_targets!(
|
|
|
|
message_bus_targets,
|
|
|
|
chat_message.chat_channel,
|
|
|
|
{ type: :flag, chat_message_id: chat_message.id, reviewable_id: reviewable.id },
|
|
|
|
permissions: {
|
|
|
|
group_ids: [Group::AUTO_GROUPS[:staff]],
|
|
|
|
},
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_to_channel!(channel, payload)
|
|
|
|
MessageBus.publish(
|
|
|
|
root_message_bus_channel(channel.id),
|
|
|
|
payload.as_json,
|
|
|
|
permissions(channel),
|
|
|
|
)
|
|
|
|
end
|
2023-04-13 22:45:50 +10:00
|
|
|
|
2023-04-24 09:32:04 +10:00
|
|
|
def self.publish_to_targets!(targets, channel, payload, permissions: nil)
|
|
|
|
targets.each do |message_bus_channel|
|
2023-04-13 22:45:50 +10:00
|
|
|
MessageBus.publish(
|
|
|
|
message_bus_channel,
|
2023-04-24 09:32:04 +10:00
|
|
|
payload.as_json,
|
|
|
|
permissions || permissions(channel),
|
2023-04-13 22:45:50 +10:00
|
|
|
)
|
|
|
|
end
|
2023-03-17 14:24:38 +01:00
|
|
|
end
|
|
|
|
|
2023-05-05 08:55:55 +02:00
|
|
|
def self.serialize_message_with_type(chat_message, type, options = {})
|
2023-04-24 09:32:04 +10:00
|
|
|
Chat::MessageSerializer
|
|
|
|
.new(chat_message, { scope: anonymous_guardian, root: :chat_message })
|
|
|
|
.as_json
|
|
|
|
.merge(type: type)
|
2023-05-05 08:55:55 +02:00
|
|
|
.merge(options)
|
2023-04-24 09:32:04 +10:00
|
|
|
end
|
|
|
|
|
2023-03-17 14:24:38 +01:00
|
|
|
def self.user_tracking_state_message_bus_channel(user_id)
|
|
|
|
"/chat/user-tracking-state/#{user_id}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_user_tracking_state(user, chat_channel_id, chat_message_id)
|
2023-05-16 14:51:13 +02:00
|
|
|
tracking_data =
|
|
|
|
Chat::TrackingState.call(
|
|
|
|
guardian: Guardian.new(user),
|
|
|
|
channel_ids: [chat_channel_id],
|
|
|
|
include_missing_memberships: true,
|
|
|
|
)
|
|
|
|
if tracking_data.failure?
|
|
|
|
raise StandardError,
|
|
|
|
"Tracking service failed when trying to publish user tracking state:\n\n#{tracking_data.inspect_steps}"
|
|
|
|
end
|
2023-03-17 14:24:38 +01:00
|
|
|
|
|
|
|
MessageBus.publish(
|
|
|
|
self.user_tracking_state_message_bus_channel(user.id),
|
2023-05-16 14:51:13 +02:00
|
|
|
{ channel_id: chat_channel_id, last_read_message_id: chat_message_id }.merge(
|
|
|
|
tracking_data.report.find_channel(chat_channel_id),
|
|
|
|
).as_json,
|
2023-03-17 14:24:38 +01:00
|
|
|
user_ids: [user.id],
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2023-03-28 09:36:28 +10:00
|
|
|
def self.bulk_user_tracking_state_message_bus_channel(user_id)
|
|
|
|
"/chat/bulk-user-tracking-state/#{user_id}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_bulk_user_tracking_state(user, channel_last_read_map)
|
2023-05-16 14:51:13 +02:00
|
|
|
tracking_data =
|
|
|
|
Chat::TrackingState.call(
|
|
|
|
guardian: Guardian.new(user),
|
2023-03-28 09:36:28 +10:00
|
|
|
channel_ids: channel_last_read_map.keys,
|
2023-05-16 14:51:13 +02:00
|
|
|
include_missing_memberships: true,
|
|
|
|
)
|
|
|
|
if tracking_data.failure?
|
|
|
|
raise StandardError,
|
|
|
|
"Tracking service failed when trying to publish bulk tracking state:\n\n#{tracking_data.inspect_steps}"
|
|
|
|
end
|
2023-03-28 09:36:28 +10:00
|
|
|
|
|
|
|
channel_last_read_map.each do |key, value|
|
2023-05-16 14:51:13 +02:00
|
|
|
channel_last_read_map[key] = value.merge(tracking_data.report.find_channel(key))
|
2023-03-28 09:36:28 +10:00
|
|
|
end
|
|
|
|
|
|
|
|
MessageBus.publish(
|
|
|
|
self.bulk_user_tracking_state_message_bus_channel(user.id),
|
|
|
|
channel_last_read_map.as_json,
|
|
|
|
user_ids: [user.id],
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2023-03-17 14:24:38 +01:00
|
|
|
def self.new_mentions_message_bus_channel(chat_channel_id)
|
|
|
|
"/chat/#{chat_channel_id}/new-mentions"
|
|
|
|
end
|
|
|
|
|
2023-03-22 10:19:59 +10:00
|
|
|
def self.kick_users_message_bus_channel(chat_channel_id)
|
|
|
|
"/chat/#{chat_channel_id}/kick"
|
|
|
|
end
|
|
|
|
|
2023-03-17 14:24:38 +01:00
|
|
|
def self.publish_new_mention(user_id, chat_channel_id, chat_message_id)
|
|
|
|
MessageBus.publish(
|
|
|
|
self.new_mentions_message_bus_channel(chat_channel_id),
|
|
|
|
{ message_id: chat_message_id, channel_id: chat_channel_id }.as_json,
|
|
|
|
user_ids: [user_id],
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
NEW_CHANNEL_MESSAGE_BUS_CHANNEL = "/chat/new-channel"
|
|
|
|
|
|
|
|
def self.publish_new_channel(chat_channel, users)
|
|
|
|
users.each do |user|
|
|
|
|
# FIXME: This could generate a lot of queries depending on the amount of users
|
|
|
|
membership = chat_channel.membership_for(user)
|
|
|
|
|
|
|
|
# TODO: this event is problematic as some code will update the membership before calling it
|
|
|
|
# and other code will update it after calling it
|
|
|
|
# it means frontend must handle logic for both cases
|
|
|
|
serialized_channel =
|
|
|
|
Chat::ChannelSerializer.new(
|
|
|
|
chat_channel,
|
|
|
|
scope: Guardian.new(user), # We need a guardian here for direct messages
|
|
|
|
root: :channel,
|
|
|
|
membership: membership,
|
|
|
|
).as_json
|
|
|
|
|
|
|
|
MessageBus.publish(NEW_CHANNEL_MESSAGE_BUS_CHANNEL, serialized_channel, user_ids: [user.id])
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.publish_inaccessible_mentions(
|
|
|
|
user_id,
|
|
|
|
chat_message,
|
|
|
|
cannot_chat_users,
|
|
|
|
without_membership,
|
|
|
|
too_many_members,
|
|
|
|
mentions_disabled
|
|
|
|
)
|
|
|
|
MessageBus.publish(
|
|
|
|
"/chat/#{chat_message.chat_channel_id}",
|
|
|
|
{
|
|
|
|
type: :mention_warning,
|
|
|
|
chat_message_id: chat_message.id,
|
|
|
|
cannot_see: cannot_chat_users.map { |u| { username: u.username, id: u.id } }.as_json,
|
|
|
|
without_membership:
|
|
|
|
without_membership.map { |u| { username: u.username, id: u.id } }.as_json,
|
|
|
|
groups_with_too_many_members: too_many_members.map(&:name).as_json,
|
|
|
|
group_mentions_disabled: mentions_disabled.map(&:name).as_json,
|
|
|
|
},
|
|
|
|
user_ids: [user_id],
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2023-03-22 10:19:59 +10:00
|
|
|
def self.publish_kick_users(channel_id, user_ids)
|
|
|
|
MessageBus.publish(
|
|
|
|
kick_users_message_bus_channel(channel_id),
|
|
|
|
{ channel_id: channel_id },
|
|
|
|
user_ids: user_ids,
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2023-03-17 14:24:38 +01:00
|
|
|
CHANNEL_EDITS_MESSAGE_BUS_CHANNEL = "/chat/channel-edits"
|
|
|
|
|
|
|
|
def self.publish_chat_channel_edit(chat_channel, acting_user)
|
|
|
|
MessageBus.publish(
|
|
|
|
CHANNEL_EDITS_MESSAGE_BUS_CHANNEL,
|
|
|
|
{
|
|
|
|
chat_channel_id: chat_channel.id,
|
|
|
|
name: chat_channel.title(acting_user),
|
|
|
|
description: chat_channel.description,
|
|
|
|
slug: chat_channel.slug,
|
|
|
|
},
|
|
|
|
permissions(chat_channel),
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
CHANNEL_STATUS_MESSAGE_BUS_CHANNEL = "/chat/channel-status"
|
|
|
|
|
|
|
|
def self.publish_channel_status(chat_channel)
|
|
|
|
MessageBus.publish(
|
|
|
|
CHANNEL_STATUS_MESSAGE_BUS_CHANNEL,
|
|
|
|
{ chat_channel_id: chat_channel.id, status: chat_channel.status },
|
|
|
|
permissions(chat_channel),
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
CHANNEL_METADATA_MESSAGE_BUS_CHANNEL = "/chat/channel-metadata"
|
|
|
|
|
|
|
|
def self.publish_chat_channel_metadata(chat_channel)
|
|
|
|
MessageBus.publish(
|
|
|
|
CHANNEL_METADATA_MESSAGE_BUS_CHANNEL,
|
|
|
|
{ chat_channel_id: chat_channel.id, memberships_count: chat_channel.user_count },
|
|
|
|
permissions(chat_channel),
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
CHANNEL_ARCHIVE_STATUS_MESSAGE_BUS_CHANNEL = "/chat/channel-archive-status"
|
|
|
|
|
|
|
|
def self.publish_archive_status(
|
|
|
|
chat_channel,
|
|
|
|
archive_status:,
|
|
|
|
archived_messages:,
|
|
|
|
archive_topic_id:,
|
|
|
|
total_messages:
|
|
|
|
)
|
|
|
|
MessageBus.publish(
|
|
|
|
CHANNEL_ARCHIVE_STATUS_MESSAGE_BUS_CHANNEL,
|
|
|
|
{
|
|
|
|
chat_channel_id: chat_channel.id,
|
|
|
|
archive_failed: archive_status == :failed,
|
|
|
|
archive_completed: archive_status == :success,
|
|
|
|
archived_messages: archived_messages,
|
|
|
|
total_messages: total_messages,
|
|
|
|
archive_topic_id: archive_topic_id,
|
|
|
|
},
|
|
|
|
permissions(chat_channel),
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def self.permissions(chat_channel)
|
|
|
|
{ user_ids: chat_channel.allowed_user_ids, group_ids: chat_channel.allowed_group_ids }
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.anonymous_guardian
|
|
|
|
Guardian.new(nil)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|