discourse/plugins/chat/app/services/update_user_last_read.rb
Loïc Guitaut 5f4623ba47 DEV: Refactor UpdateUserLastRead a little
We’re now using `contract` as the first step and validations for
mandatory parameters have been added.

To simplify specs a bit, we only assert the service contract is run as
expected without testing each validation case. We’re now testing the
contract itself in isolation.
2023-02-13 17:03:41 +01:00

82 lines
2.7 KiB
Ruby

# frozen_string_literal: true
module Chat
module Service
# Service responsible for updating the last read message id of a membership.
#
# @example
# Chat::Service::UpdateUserLastRead.call(user_id: 1, channel_id: 2, message_id: 3, guardian: guardian)
#
class UpdateUserLastRead
include Base
# @!method call(user_id:, channel_id:, message_id:, guardian:)
# @param [Integer] user_id
# @param [Integer] channel_id
# @param [Integer] message_id
# @param [Guardian] guardian
# @return [Chat::Service::Base::Context]
contract
model :membership, :fetch_active_membership
policy :invalid_access
policy :ensure_message_id_recency
policy :ensure_message_exists
step :update_last_read_message_id
step :mark_associated_mentions_as_read
step :publish_new_last_read_to_clients
# @!visibility private
class Contract
attribute :message_id, :integer
attribute :user_id, :integer
attribute :channel_id, :integer
validates :message_id, :user_id, :channel_id, presence: true
end
private
def fetch_active_membership(user_id:, channel_id:, **)
UserChatChannelMembership.includes(:user, :chat_channel).find_by(
user_id: user_id,
chat_channel_id: channel_id,
following: true,
)
end
def invalid_access(guardian:, membership:, **)
guardian.can_join_chat_channel?(membership.chat_channel)
end
def ensure_message_id_recency(message_id:, membership:, **)
!membership.last_read_message_id || message_id >= membership.last_read_message_id
end
def ensure_message_exists(channel_id:, message_id:, **)
ChatMessage.with_deleted.exists?(chat_channel_id: channel_id, id: message_id)
end
def update_last_read_message_id(message_id:, membership:, **)
membership.update!(last_read_message_id: message_id)
end
def mark_associated_mentions_as_read(membership:, message_id:, **)
Notification
.where(notification_type: Notification.types[:chat_mention])
.where(user: membership.user)
.where(read: false)
.joins("INNER JOIN chat_mentions ON chat_mentions.notification_id = notifications.id")
.joins("INNER JOIN chat_messages ON chat_mentions.chat_message_id = chat_messages.id")
.where("chat_messages.id <= ?", message_id)
.where("chat_messages.chat_channel_id = ?", membership.chat_channel.id)
.update_all(read: true)
end
def publish_new_last_read_to_clients(guardian:, channel_id:, message_id:, **)
ChatPublisher.publish_user_tracking_state(guardian.user, channel_id, message_id)
end
end
end
end