DEV: Provide user input to services using params key

Currently in services, we don’t make a distinction between input
parameters, options and dependencies.

This can lead to user input modifying the service behavior, whereas it
was not the developer intention.

This patch addresses the issue by changing how data is provided to
services:
- `params` is now used to hold all data coming from outside (typically
  user input from a controller) and a contract will take its values from
  `params`.
- `options` is a new key to provide options to a service. This typically
  allows changing a service behavior at runtime. It is, of course,
  totally optional.
- `dependencies` is actually anything else provided to the service (like
  `guardian`) and available directly from the context object.

The `service_params` helper in controllers has been updated to reflect
those changes, so most of the existing services didn’t need specific
changes.

The options block has the same DSL as contracts, as it’s also based on
`ActiveModel`. There aren’t any validations, though. Here’s an example:
```ruby
options do
  attribute :allow_changing_hidden, :boolean, default: false
end
```
And here’s an example of how to call a service with the new keys:
```ruby
MyService.call(params: { key1: value1, … }, options: { my_option: true }, guardian:, …)
```
This commit is contained in:
Loïc Guitaut 2024-10-18 17:45:47 +02:00 committed by Loïc Guitaut
parent a89767913d
commit 41584ab40c
115 changed files with 1152 additions and 895 deletions

View File

@ -38,14 +38,18 @@ class Admin::Config::AboutController < Admin::AdminController
settings_map.each do |name, value| settings_map.each do |name, value|
SiteSetting::Update.call( SiteSetting::Update.call(
guardian:, guardian:,
setting_name: name, params: {
new_value: value, setting_name: name,
allow_changing_hidden: %i[ new_value: value,
extended_site_description },
extended_site_description_cooked options: {
about_banner_image allow_changing_hidden: %i[
community_owner extended_site_description
].include?(name), extended_site_description_cooked
about_banner_image
community_owner
].include?(name),
},
) )
end end
render json: success_json render json: success_json

View File

@ -39,7 +39,7 @@ class Admin::SiteSettingsController < Admin::AdminController
previous_value = value_or_default(SiteSetting.get(id)) if update_existing_users previous_value = value_or_default(SiteSetting.get(id)) if update_existing_users
SiteSetting::Update.call(service_params.merge(setting_name: id, new_value: value)) do SiteSetting::Update.call(params: { setting_name: id, new_value: value }, guardian:) do
on_success do |contract:| on_success do |contract:|
if update_existing_users if update_existing_users
SiteSettingUpdateExistingUsers.call(id, contract.new_value, previous_value) SiteSettingUpdateExistingUsers.call(id, contract.new_value, previous_value)

View File

@ -1164,6 +1164,6 @@ class ApplicationController < ActionController::Base
end end
def service_params def service_params
params.to_unsafe_h.merge(guardian:) { params: params.to_unsafe_h, guardian: }
end end
end end

View File

@ -16,8 +16,8 @@ class AdminNotices::Dismiss
guardian.is_admin? guardian.is_admin?
end end
def fetch_admin_notice(id:) def fetch_admin_notice(params:)
AdminNotice.find_by(id: id) AdminNotice.find_by(id: params[:id])
end end
def destroy(admin_notice:) def destroy(admin_notice:)

View File

@ -24,7 +24,7 @@ class Experiments::Toggle
def toggle(contract:, guardian:) def toggle(contract:, guardian:)
SiteSetting.set_and_log( SiteSetting.set_and_log(
contract.setting_name, contract.setting_name,
!SiteSetting.send(contract.setting_name), !SiteSetting.public_send(contract.setting_name),
guardian.user, guardian.user,
) )
end end

View File

@ -14,8 +14,8 @@ class Flags::DestroyFlag
private private
def fetch_flag(id:) def fetch_flag(params:)
Flag.find_by(id: id) Flag.find_by(id: params[:id])
end end
def not_system(flag:) def not_system(flag:)

View File

@ -3,11 +3,12 @@
class SiteSetting::Update class SiteSetting::Update
include Service::Base include Service::Base
options { attribute :allow_changing_hidden, :boolean, default: false }
policy :current_user_is_admin policy :current_user_is_admin
contract do contract do
attribute :setting_name attribute :setting_name
attribute :new_value attribute :new_value
attribute :allow_changing_hidden, :boolean, default: false
before_validation do before_validation do
self.setting_name = setting_name&.to_sym self.setting_name = setting_name&.to_sym
@ -43,8 +44,8 @@ class SiteSetting::Update
guardian.is_admin? guardian.is_admin?
end end
def setting_is_visible(contract:) def setting_is_visible(contract:, options:)
contract.allow_changing_hidden || !SiteSetting.hidden_settings.include?(contract.setting_name) options.allow_changing_hidden || !SiteSetting.hidden_settings.include?(contract.setting_name)
end end
def setting_is_configurable(contract:) def setting_is_configurable(contract:)

View File

@ -40,9 +40,9 @@ module Service
# #
# @example An example from the {TrashChannel} service # @example An example from the {TrashChannel} service
# class TrashChannel # class TrashChannel
# include Base # include Service::Base
# #
# model :channel, :fetch_channel # model :channel
# policy :invalid_access # policy :invalid_access
# transaction do # transaction do
# step :prevents_slug_collision # step :prevents_slug_collision
@ -79,17 +79,15 @@ module Service
# end # end
# @example An example from the {UpdateChannelStatus} service which uses a contract # @example An example from the {UpdateChannelStatus} service which uses a contract
# class UpdateChannelStatus # class UpdateChannelStatus
# include Base # include Service::Base
# #
# model :channel, :fetch_channel # model :channel
# contract # contract do
# policy :check_channel_permission
# step :change_status
#
# class Contract
# attribute :status # attribute :status
# validates :status, inclusion: { in: Chat::Channel.editable_statuses.keys } # validates :status, inclusion: { in: Chat::Channel.editable_statuses.keys }
# end # end
# policy :check_channel_permission
# step :change_status
# #
# … # …
# end # end

View File

@ -18,7 +18,7 @@ module Service
# Simple structure to hold the context of the service during its whole lifecycle. # Simple structure to hold the context of the service during its whole lifecycle.
class Context class Context
delegate :slice, to: :store delegate :slice, :dig, to: :store
def initialize(context = {}) def initialize(context = {})
@store = context.symbolize_keys @store = context.symbolize_keys
@ -115,6 +115,12 @@ module Service
def transaction(&block) def transaction(&block)
steps << TransactionStep.new(&block) steps << TransactionStep.new(&block)
end end
def options(&block)
klass = Class.new(Service::OptionsBase).tap { _1.class_eval(&block) }
const_set("Options", klass)
steps << OptionsStep.new(:default, class_name: klass)
end
end end
# @!visibility private # @!visibility private
@ -196,7 +202,7 @@ module Service
attributes = class_name.attribute_names.map(&:to_sym) attributes = class_name.attribute_names.map(&:to_sym)
default_values = {} default_values = {}
default_values = context[default_values_from].slice(*attributes) if default_values_from default_values = context[default_values_from].slice(*attributes) if default_values_from
contract = class_name.new(default_values.merge(context.slice(*attributes))) contract = class_name.new(default_values.merge(context[:params].slice(*attributes)))
context[contract_name] = contract context[contract_name] = contract
context[result_key] = Context.build context[result_key] = Context.build
if contract.invalid? if contract.invalid?
@ -208,9 +214,13 @@ module Service
private private
def contract_name def contract_name
return :contract if name.to_sym == :default return :contract if default?
:"#{name}_contract" :"#{name}_contract"
end end
def default?
name.to_sym == :default
end
end end
# @!visibility private # @!visibility private
@ -229,6 +239,14 @@ module Service
end end
end end
# @!visibility private
class OptionsStep < Step
def call(instance, context)
context[result_key] = Context.build
context[:options] = class_name.new(context[:options])
end
end
included do included do
# The global context which is available from any step. # The global context which is available from any step.
attr_reader :context attr_reader :context
@ -263,7 +281,7 @@ module Service
# customized by providing the +name+ argument). # customized by providing the +name+ argument).
# #
# @example # @example
# model :channel, :fetch_channel # model :channel
# #
# private # private
# #
@ -361,6 +379,17 @@ module Service
# step :log_channel_deletion # step :log_channel_deletion
# end # end
# @!scope class
# @!method options(&block)
# @param block [Proc] a block containing options definition
# This is used to define options allowing to parameterize the service
# behavior. The resulting options are available in `context[:options]`.
#
# @example
# options do
# attribute :my_option, :boolean, default: false
# end
# @!visibility private # @!visibility private
def initialize(initial_context = {}) def initialize(initial_context = {})
@context = Context.build(initial_context.merge(__steps__: self.class.steps)) @context = Context.build(initial_context.merge(__steps__: self.class.steps))

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class Service::OptionsBase
include ActiveModel::API
include ActiveModel::Attributes
include ActiveModel::AttributeMethods
end

View File

@ -98,6 +98,10 @@ class Service::StepsInspector
nil nil
end end
end end
#
# @!visibility private
class Options < Step
end
attr_reader :steps, :result attr_reader :steps, :result

View File

@ -67,8 +67,7 @@ class Chat::Api::ChannelMessagesController < Chat::ApiController
def create def create
Chat::MessageRateLimiter.run!(current_user) Chat::MessageRateLimiter.run!(current_user)
# users can't force a thread through JSON API Chat::CreateMessage.call(service_params) do
Chat::CreateMessage.call(service_params.merge(force_thread: false)) do
on_success do |message_instance:| on_success do |message_instance:|
render json: success_json.merge(message_id: message_instance.id) render json: success_json.merge(message_id: message_instance.id)
end end

View File

@ -55,7 +55,7 @@ class Chat::Api::ChannelsController < Chat::ApiController
# at the moment. This may change in future, at which point we will need to pass in # at the moment. This may change in future, at which point we will need to pass in
# a chatable_type param as well and switch to the correct service here. # a chatable_type param as well and switch to the correct service here.
Chat::CreateCategoryChannel.call( Chat::CreateCategoryChannel.call(
service_params.merge(channel_params.merge(category_id: channel_params[:chatable_id])), service_params.merge(params: channel_params.merge(category_id: channel_params[:chatable_id])),
) do ) do
on_success do |channel:, membership:| on_success do |channel:, membership:|
render_serialized(channel, Chat::ChannelSerializer, root: "channel", membership:) render_serialized(channel, Chat::ChannelSerializer, root: "channel", membership:)
@ -95,7 +95,7 @@ class Chat::Api::ChannelsController < Chat::ApiController
auto_join_limiter(channel_from_params).performed! auto_join_limiter(channel_from_params).performed!
end end
Chat::UpdateChannel.call(service_params.merge(params_to_edit)) do Chat::UpdateChannel.call(service_params.deep_merge(params: params_to_edit.to_unsafe_h)) do
on_success do |channel:| on_success do |channel:|
render_serialized( render_serialized(
channel, channel,

View File

@ -56,12 +56,12 @@ module Chat
webhook.chat_channel.add(Discourse.system_user) webhook.chat_channel.add(Discourse.system_user)
Chat::CreateMessage.call( Chat::CreateMessage.call(
service_params.merge( params: {
chat_channel_id: webhook.chat_channel_id, chat_channel_id: webhook.chat_channel_id,
guardian: Discourse.system_user.guardian,
message: text, message: text,
incoming_chat_webhook: webhook, },
), guardian: Discourse.system_user.guardian,
incoming_chat_webhook: webhook,
) do ) do
on_success { render json: success_json } on_success { render json: success_json }
on_failure { render(json: failed_json, status: 422) } on_failure { render(json: failed_json, status: 422) }

View File

@ -3,8 +3,8 @@
module Jobs module Jobs
module Chat module Chat
class AutoJoinChannelBatch < ::Jobs::Base class AutoJoinChannelBatch < ::Jobs::Base
def execute(*) def execute(args)
::Chat::AutoJoinChannelBatch.call(*) do ::Chat::AutoJoinChannelBatch.call(params: args) do
on_failure { Rails.logger.error("Failed with unexpected error") } on_failure { Rails.logger.error("Failed with unexpected error") }
on_failed_contract do |contract| on_failed_contract do |contract|
Rails.logger.error(contract.errors.full_messages.join(", ")) Rails.logger.error(contract.errors.full_messages.join(", "))

View File

@ -4,7 +4,7 @@ module Jobs
module Chat module Chat
class AutoRemoveMembershipHandleCategoryUpdated < ::Jobs::Base class AutoRemoveMembershipHandleCategoryUpdated < ::Jobs::Base
def execute(args) def execute(args)
::Chat::AutoRemove::HandleCategoryUpdated.call(**args) ::Chat::AutoRemove::HandleCategoryUpdated.call(params: args)
end end
end end
end end

View File

@ -4,7 +4,7 @@ module Jobs
module Chat module Chat
class AutoRemoveMembershipHandleChatAllowedGroupsChange < ::Jobs::Base class AutoRemoveMembershipHandleChatAllowedGroupsChange < ::Jobs::Base
def execute(args) def execute(args)
::Chat::AutoRemove::HandleChatAllowedGroupsChange.call(**args) ::Chat::AutoRemove::HandleChatAllowedGroupsChange.call(params: args)
end end
end end
end end

View File

@ -4,7 +4,7 @@ module Jobs
module Chat module Chat
class AutoRemoveMembershipHandleDestroyedGroup < ::Jobs::Base class AutoRemoveMembershipHandleDestroyedGroup < ::Jobs::Base
def execute(args) def execute(args)
::Chat::AutoRemove::HandleDestroyedGroup.call(**args) ::Chat::AutoRemove::HandleDestroyedGroup.call(params: args)
end end
end end
end end

View File

@ -4,7 +4,7 @@ module Jobs
module Chat module Chat
class AutoRemoveMembershipHandleUserRemovedFromGroup < ::Jobs::Base class AutoRemoveMembershipHandleUserRemovedFromGroup < ::Jobs::Base
def execute(args) def execute(args)
::Chat::AutoRemove::HandleUserRemovedFromGroup.call(**args) ::Chat::AutoRemove::HandleUserRemovedFromGroup.call(params: args)
end end
end end
end end

View File

@ -8,19 +8,21 @@ module Chat
# @example # @example
# ::Chat::AddUsersToChannel.call( # ::Chat::AddUsersToChannel.call(
# guardian: guardian, # guardian: guardian,
# channel_id: 1, # params: {
# usernames: ["bob", "alice"] # channel_id: 1,
# usernames: ["bob", "alice"],
# }
# ) # )
# #
class AddUsersToChannel class AddUsersToChannel
include Service::Base include Service::Base
# @!method call(guardian:, **params_to_create) # @!method self.call(guardian:, params:)
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Integer] id of the channel # @param [Hash] params
# @param [Hash] params_to_create # @option params [Integer] :channel_id ID of the channel
# @option params_to_create [Array<String>] usernames # @option params [Array<String>] :usernames
# @option params_to_create [Array<String>] groups # @option params [Array<String>] :groups
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do
attribute :usernames, :array attribute :usernames, :array
@ -123,14 +125,16 @@ module Chat
::Chat::CreateMessage.call( ::Chat::CreateMessage.call(
guardian: Discourse.system_user.guardian, guardian: Discourse.system_user.guardian,
chat_channel_id: channel.id, params: {
message: chat_channel_id: channel.id,
I18n.t( message:
"chat.channel.users_invited_to_channel", I18n.t(
invited_users: added_users.map { |u| "@#{u.username}" }.join(", "), "chat.channel.users_invited_to_channel",
inviting_user: "@#{guardian.user.username}", invited_users: added_users.map { |u| "@#{u.username}" }.join(", "),
count: added_users.count, inviting_user: "@#{guardian.user.username}",
), count: added_users.count,
),
},
) { on_failure { fail!(failure: "Failed to notice the channel") } } ) { on_failure { fail!(failure: "Failed to notice the channel") } }
end end
end end

View File

@ -6,9 +6,11 @@ module Chat
# #
# @example # @example
# Chat::AutoJoinChannelBatch.call( # Chat::AutoJoinChannelBatch.call(
# channel_id: 1, # params: {
# start_user_id: 27, # channel_id: 1,
# end_user_id: 58, # start_user_id: 27,
# end_user_id: 58,
# }
# ) # )
# #
class AutoJoinChannelBatch class AutoJoinChannelBatch

View File

@ -6,25 +6,27 @@ module Chat
# @example # @example
# Service::Chat::CreateCategoryChannel.call( # Service::Chat::CreateCategoryChannel.call(
# guardian: guardian, # guardian: guardian,
# name: "SuperChannel", # params: {
# description: "This is the best channel", # name: "SuperChannel",
# slug: "super-channel", # description: "This is the best channel",
# category_id: category.id, # slug: "super-channel",
# threading_enabled: true, # category_id: category.id,
# threading_enabled: true,
# }
# ) # )
# #
class CreateCategoryChannel class CreateCategoryChannel
include Service::Base include Service::Base
# @!method call(guardian:, **params_to_create) # @!method self.call(guardian:, params:)
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params_to_create # @param [Hash] params
# @option params_to_create [String] name # @option params [String] :name
# @option params_to_create [String] description # @option params [String] :description
# @option params_to_create [String] slug # @option params [String] :slug
# @option params_to_create [Boolean] auto_join_users # @option params [Boolean] :auto_join_users
# @option params_to_create [Integer] category_id # @option params [Integer] :category_id
# @option params_to_create [Boolean] threading_enabled # @option params [Boolean] :threading_enabled
# @return [Service::Base::Context] # @return [Service::Base::Context]
policy :public_channels_enabled policy :public_channels_enabled

View File

@ -9,18 +9,20 @@ module Chat
# @example # @example
# ::Chat::CreateDirectMessageChannel.call( # ::Chat::CreateDirectMessageChannel.call(
# guardian: guardian, # guardian: guardian,
# target_usernames: ["bob", "alice"] # params: {
# target_usernames: ["bob", "alice"],
# },
# ) # )
# #
class CreateDirectMessageChannel class CreateDirectMessageChannel
include Service::Base include Service::Base
# @!method call(guardian:, **params_to_create) # @!method self.call(guardian:, params:)
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params_to_create # @param [Hash] params
# @option params_to_create [Array<String>] target_usernames # @option params [Array<String>] :target_usernames
# @option params_to_create [Array<String>] target_groups # @option params [Array<String>] :target_groups
# @option params_to_create [Boolean] upsert # @option params [Boolean] :upsert
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -4,22 +4,34 @@ module Chat
# Service responsible for creating a new message. # Service responsible for creating a new message.
# #
# @example # @example
# Chat::CreateMessage.call(chat_channel_id: 2, guardian: guardian, message: "A new message") # Chat::CreateMessage.call(params: { chat_channel_id: 2, message: "A new message" }, guardian: guardian)
# #
class CreateMessage class CreateMessage
include Service::Base include Service::Base
# @!method call(chat_channel_id:, guardian:, in_reply_to_id:, message:, staged_id:, upload_ids:, thread_id:, incoming_chat_webhook:) # @!method self.call(guardian:, params:, options:)
# @param guardian [Guardian] # @param guardian [Guardian]
# @param chat_channel_id [Integer] # @param [Hash] params
# @param message [String] # @option params [Integer] :chat_channel_id
# @param in_reply_to_id [Integer] ID of a message to reply to # @option params [String] :message
# @param thread_id [Integer] ID of a thread to reply to # @option params [Integer] :in_reply_to_id ID of a message to reply to
# @param upload_ids [Array<Integer>] IDs of uploaded documents # @option params [Integer] :thread_id ID of a thread to reply to
# @param context_topic_id [Integer] ID of the currently visible topic in drawer mode # @option params [Array<Integer>] :upload_ids IDs of uploaded documents
# @param context_post_ids [Array<Integer>] IDs of the currently visible posts in drawer mode # @option params [Integer] :context_topic_id ID of the currently visible topic in drawer mode
# @param staged_id [String] arbitrary string that will be sent back to the client # @option params [Array<Integer>] :context_post_ids IDs of the currently visible posts in drawer mode
# @param incoming_chat_webhook [Chat::IncomingWebhook] # @option params [String] :staged_id arbitrary string that will be sent back to the client
# @param [Hash] options
# @option options [Chat::IncomingWebhook] :incoming_chat_webhook
# @return [Service::Base::Context]
options do
attribute :streaming, :boolean, default: false
attribute :enforce_membership, :boolean, default: false
attribute :process_inline, :boolean, default: -> { Rails.env.test? }
attribute :force_thread, :boolean, default: false
attribute :strip_whitespaces, :boolean, default: true
attribute :created_by_sdk, :boolean, default: false
end
policy :no_silenced_user policy :no_silenced_user
contract do contract do
@ -31,13 +43,6 @@ module Chat
attribute :staged_id, :string attribute :staged_id, :string
attribute :upload_ids, :array attribute :upload_ids, :array
attribute :thread_id, :string attribute :thread_id, :string
attribute :streaming, :boolean, default: false
attribute :enforce_membership, :boolean, default: false
attribute :incoming_chat_webhook
attribute :process_inline, :boolean, default: Rails.env.test?
attribute :force_thread, :boolean, default: false
attribute :strip_whitespaces, :boolean, default: true
attribute :created_by_sdk, :boolean, default: false
validates :chat_channel_id, presence: true validates :chat_channel_id, presence: true
validates :message, presence: true, if: -> { upload_ids.blank? } validates :message, presence: true, if: -> { upload_ids.blank? }
@ -79,8 +84,8 @@ module Chat
Chat::Channel.find_by_id_or_slug(contract.chat_channel_id) Chat::Channel.find_by_id_or_slug(contract.chat_channel_id)
end end
def enforce_membership(guardian:, channel:, contract:) def enforce_membership(guardian:, channel:, options:)
if guardian.user.bot? || contract.enforce_membership if guardian.user.bot? || options.enforce_membership
channel.add(guardian.user) channel.add(guardian.user)
if channel.direct_message_channel? if channel.direct_message_channel?
@ -102,7 +107,7 @@ module Chat
reply&.chat_channel == channel reply&.chat_channel == channel
end end
def fetch_thread(contract:, reply:, channel:) def fetch_thread(contract:, reply:, channel:, options:)
return Chat::Thread.find_by(id: contract.thread_id) if contract.thread_id.present? return Chat::Thread.find_by(id: contract.thread_id) if contract.thread_id.present?
return unless reply return unless reply
reply.thread || reply.thread ||
@ -110,7 +115,7 @@ module Chat
original_message: reply, original_message: reply,
original_message_user: reply.user, original_message_user: reply.user,
channel: channel, channel: channel,
force: contract.force_thread, force: options.force_thread,
) )
end end
@ -129,16 +134,16 @@ module Chat
guardian.user.uploads.where(id: contract.upload_ids) guardian.user.uploads.where(id: contract.upload_ids)
end end
def clean_message(contract:) def clean_message(contract:, options:)
contract.message = contract.message =
TextCleaner.clean( TextCleaner.clean(
contract.message, contract.message,
strip_whitespaces: contract.strip_whitespaces, strip_whitespaces: options.strip_whitespaces,
strip_zero_width_spaces: true, strip_zero_width_spaces: true,
) )
end end
def instantiate_message(channel:, guardian:, contract:, uploads:, thread:, reply:) def instantiate_message(channel:, guardian:, contract:, uploads:, thread:, reply:, options:)
channel.chat_messages.new( channel.chat_messages.new(
user: guardian.user, user: guardian.user,
last_editor: guardian.user, last_editor: guardian.user,
@ -148,7 +153,7 @@ module Chat
thread: thread, thread: thread,
cooked: ::Chat::Message.cook(contract.message, user_id: guardian.user.id), cooked: ::Chat::Message.cook(contract.message, user_id: guardian.user.id),
cooked_version: ::Chat::Message::BAKED_VERSION, cooked_version: ::Chat::Message::BAKED_VERSION,
streaming: contract.streaming, streaming: options.streaming,
) )
end end
@ -169,10 +174,10 @@ module Chat
thread.add(thread.original_message_user) thread.add(thread.original_message_user)
end end
def create_webhook_event(contract:, message_instance:) def create_webhook_event(message_instance:)
return if contract.incoming_chat_webhook.blank? return if context[:incoming_chat_webhook].blank?
message_instance.create_chat_webhook_event( message_instance.create_chat_webhook_event(
incoming_chat_webhook: contract.incoming_chat_webhook, incoming_chat_webhook: context[:incoming_chat_webhook],
) )
end end
@ -186,8 +191,8 @@ module Chat
membership.update!(last_read_message: message_instance) membership.update!(last_read_message: message_instance)
end end
def update_created_by_sdk(message_instance:, contract:) def update_created_by_sdk(message_instance:, options:)
message_instance.created_by_sdk = contract.created_by_sdk message_instance.created_by_sdk = options.created_by_sdk
end end
def process_direct_message_channel(membership:) def process_direct_message_channel(membership:)
@ -200,7 +205,7 @@ module Chat
Chat::Publisher.publish_thread_created!(channel, reply, thread.id) Chat::Publisher.publish_thread_created!(channel, reply, thread.id)
end end
def process(channel:, message_instance:, contract:, thread:) def process(channel:, message_instance:, contract:, thread:, options:)
::Chat::Publisher.publish_new!(channel, message_instance, contract.staged_id) ::Chat::Publisher.publish_new!(channel, message_instance, contract.staged_id)
DiscourseEvent.trigger( DiscourseEvent.trigger(
@ -218,7 +223,7 @@ module Chat
}, },
) )
if contract.process_inline if options.process_inline
Jobs::Chat::ProcessMessage.new.execute( Jobs::Chat::ProcessMessage.new.execute(
{ chat_message_id: message_instance.id, staged_id: contract.staged_id }, { chat_message_id: message_instance.id, staged_id: contract.staged_id },
) )

View File

@ -4,16 +4,17 @@ module Chat
# Creates a thread. # Creates a thread.
# #
# @example # @example
# Chat::CreateThread.call(channel_id: 2, original_message_id: 3, guardian: guardian, title: "Restaurant for Saturday") # Chat::CreateThread.call(guardian: guardian, params: { channel_id: 2, original_message_id: 3, title: "Restaurant for Saturday" })
# #
class CreateThread class CreateThread
include Service::Base include Service::Base
# @!method call(thread_id:, channel_id:, guardian:, **params_to_create) # @!method self.call(guardian:, params:)
# @param [Integer] original_message_id
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @option params_to_create [String,nil] title # @param [Hash] params
# @option params [Integer] :original_message_id
# @option params [Integer] :channel_id
# @option params [String,nil] :title
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -6,26 +6,27 @@ module Chat
# @example # @example
# ::Chat::FlagMessage.call( # ::Chat::FlagMessage.call(
# guardian: guardian, # guardian: guardian,
# channel_id: 1, # params: {
# message_id: 43, # channel_id: 1,
# message_id: 43,
# }
# ) # )
# #
class FlagMessage class FlagMessage
include Service::Base include Service::Base
# @!method call(guardian:, channel_id:, data:) # @!method self.call(guardian:, params:)
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Integer] channel_id of the channel # @param [Hash] params
# @param [Integer] message_id of the message # @option params [Integer] :channel_id of the channel
# @param [Integer] flag_type_id - Type of flag to create # @option params [Integer] :message_id of the message
# @param [String] optional message - Used when the flag type is notify_user or notify_moderators and we have to create # @option params [Integer] :flag_type_id Type of flag to create
# a separate PM. # @option params [String] :message (optional) Used when the flag type is notify_user or notify_moderators and we have to create a separate PM.
# @param [Boolean] optional is_warning - Staff can send warnings when using the notify_user flag. # @option params [Boolean] :is_warning (optional) Staff can send warnings when using the notify_user flag.
# @param [Boolean] optional take_action - Automatically approves the created reviewable and deletes the chat message. # @option params [Boolean] :take_action (optional) Automatically approves the created reviewable and deletes the chat message.
# @param [Boolean] optional queue_for_review - Adds a special reason to the reviewable score and creates the reviewable using # @option params [Boolean] :queue_for_review (optional) Adds a special reason to the reviewable score and creates the reviewable using the force_review option.
# the force_review option.
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do
attribute :message_id, :integer attribute :message_id, :integer
attribute :channel_id, :integer attribute :channel_id, :integer

View File

@ -4,16 +4,17 @@ module Chat
# Invites users to a channel. # Invites users to a channel.
# #
# @example # @example
# Chat::InviteUsersToChannel.call(channel_id: 2, user_ids: [2, 43], guardian: guardian, **optional_params) # Chat::InviteUsersToChannel.call(params: { channel_id: 2, user_ids: [2, 43] }, guardian: guardian)
# #
class InviteUsersToChannel class InviteUsersToChannel
include Service::Base include Service::Base
# @!method call(user_ids:, channel_id:, guardian:) # @!method self.call(guardian:, params:)
# @param [Array<Integer>] user_ids
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @option optional_params [Integer, nil] message_id # @param [Hash] params
# @option params [Array<Integer>] :user_ids
# @option params [Integer] :channel_id
# @option params [Integer, nil] :message_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -6,17 +6,20 @@ module Chat
# @example # @example
# ::Chat::LeaveChannel.call( # ::Chat::LeaveChannel.call(
# guardian: guardian, # guardian: guardian,
# channel_id: 1, # params: {
# channel_id: 1,
# }
# ) # )
# #
class LeaveChannel class LeaveChannel
include Service::Base include Service::Base
# @!method call(guardian:, channel_id:,) # @!method self.call(guardian:, params:)
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Integer] channel_id of the channel # @param [Hash] params
# @option params [Integer] :channel_id ID of the channel
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do
attribute :channel_id, :integer attribute :channel_id, :integer

View File

@ -5,14 +5,15 @@ module Chat
# or fetching paginated messages from last read. # or fetching paginated messages from last read.
# #
# @example # @example
# Chat::ListChannelMessages.call(channel_id: 2, guardian: guardian, **optional_params) # Chat::ListChannelMessages.call(params: { channel_id: 2, **optional_params }, guardian: guardian)
# #
class ListChannelMessages class ListChannelMessages
include Service::Base include Service::Base
# @!method call(guardian:) # @!method self.call(guardian:, params:)
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -5,14 +5,15 @@ module Chat
# or fetching paginated messages from last read. # or fetching paginated messages from last read.
# #
# @example # @example
# Chat::ListThreadMessages.call(thread_id: 2, guardian: guardian, **optional_params) # Chat::ListThreadMessages.call(params: { thread_id: 2, **optional_params }, guardian: guardian)
# #
class ListChannelThreadMessages class ListChannelThreadMessages
include Service::Base include Service::Base
# @!method call(guardian:) # @!method self.call(guardian:, params:)
# @param [Guardian] guardian # @param [Guardian] guardian
# @option optional_params [Integer] thread_id # @param [Hash] params
# @option params [Integer] :thread_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -4,12 +4,12 @@ module Chat
# List of the channels a user is tracking # List of the channels a user is tracking
# #
# @example # @example
# Chat::ListUserChannels.call(guardian: guardian, **optional_params) # Chat::ListUserChannels.call(guardian:)
# #
class ListUserChannels class ListUserChannels
include Service::Base include Service::Base
# @!method call(guardian:) # @!method self.call(guardian:)
# @param [Guardian] guardian # @param [Guardian] guardian
# @return [Service::Base::Context] # @return [Service::Base::Context]

View File

@ -10,18 +10,19 @@ module Chat
# of normal or tracking will be returned. # of normal or tracking will be returned.
# #
# @example # @example
# Chat::LookupChannelThreads.call(channel_id: 2, guardian: guardian, limit: 5, offset: 2) # Chat::LookupChannelThreads.call(params: { channel_id: 2, limit: 5, offset: 2 }, guardian: guardian)
# #
class LookupChannelThreads class LookupChannelThreads
include Service::Base include Service::Base
THREADS_LIMIT = 10 THREADS_LIMIT = 10
# @!method call(channel_id:, guardian:, limit: nil, offset: nil) # @!method self.call(guardian:, params:)
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Integer] limit # @param [Hash] params
# @param [Integer] offset # @option params [Integer] :channel_id
# @option params [Integer] :limit
# @option params [Integer] :offset
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -5,15 +5,16 @@ module Chat
# match, and the channel must specifically have threading enabled. # match, and the channel must specifically have threading enabled.
# #
# @example # @example
# Chat::LookupThread.call(thread_id: 88, channel_id: 2, guardian: guardian) # Chat::LookupThread.call(params: { thread_id: 88, channel_id: 2 }, guardian: guardian)
# #
class LookupThread class LookupThread
include Service::Base include Service::Base
# @!method call(thread_id:, channel_id:, guardian:) # @!method self.call(guardian:, params:)
# @param [Integer] thread_id
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Integer] :thread_id
# @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -7,17 +7,18 @@ module Chat
# of normal or tracking will be returned. # of normal or tracking will be returned.
# #
# @example # @example
# Chat::LookupUserThreads.call(guardian: guardian, limit: 5, offset: 2) # Chat::LookupUserThreads.call(guardian: guardian, params: { limit: 5, offset: 2 })
# #
class LookupUserThreads class LookupUserThreads
include Service::Base include Service::Base
THREADS_LIMIT = 10 THREADS_LIMIT = 10
# @!method call(guardian:, limit: nil, offset: nil) # @!method self.call(guardian:, params:)
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Integer] limit # @param [Hash] params
# @param [Integer] offset # @option params [Integer] :limit
# @option params [Integer] :offset
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -10,7 +10,7 @@ module Chat
class MarkAllUserChannelsRead class MarkAllUserChannelsRead
include ::Service::Base include ::Service::Base
# @!method call(guardian:) # @!method self.call(guardian:)
# @param [Guardian] guardian # @param [Guardian] guardian
# @return [Service::Base::Context] # @return [Service::Base::Context]

View File

@ -7,18 +7,21 @@ module Chat
# #
# @example # @example
# Chat::MarkThreadTitlePromptSeen.call( # Chat::MarkThreadTitlePromptSeen.call(
# thread_id: 88, # params: {
# channel_id: 2, # thread_id: 88,
# channel_id: 2,
# },
# guardian: guardian, # guardian: guardian,
# ) # )
# #
class MarkThreadTitlePromptSeen class MarkThreadTitlePromptSeen
include Service::Base include Service::Base
# @!method call(thread_id:, channel_id:, guardian:) # @!method self.call(guardian:, params:)
# @param [Integer] thread_id
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Integer] :thread_id
# @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -322,8 +322,10 @@ module Chat
tracking_data = tracking_data =
Chat::TrackingState.call( Chat::TrackingState.call(
guardian: Guardian.new(user), guardian: Guardian.new(user),
channel_ids: channel_last_read_map.keys, params: {
include_missing_memberships: true, channel_ids: channel_last_read_map.keys,
include_missing_memberships: true,
},
) )
if tracking_data.failure? if tracking_data.failure?
raise StandardError, raise StandardError,

View File

@ -6,15 +6,16 @@ module Chat
# updated. # updated.
# #
# @example # @example
# Chat::RestoreMessage.call(message_id: 2, channel_id: 1, guardian: guardian) # Chat::RestoreMessage.call(params: { message_id: 2, channel_id: 1 }, guardian: guardian)
# #
class RestoreMessage class RestoreMessage
include Service::Base include Service::Base
# @!method call(message_id:, channel_id:, guardian:) # @!method self.call(guardian:, params:)
# @param [Integer] message_id
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Integer] :message_id
# @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -4,16 +4,17 @@ module Chat
# Returns a list of chatables (users, groups ,category channels, direct message channels) that can be chatted with. # Returns a list of chatables (users, groups ,category channels, direct message channels) that can be chatted with.
# #
# @example # @example
# Chat::SearchChatable.call(term: "@bob", guardian: guardian) # Chat::SearchChatable.call(params: { term: "@bob" }, guardian: guardian)
# #
class SearchChatable class SearchChatable
include Service::Base include Service::Base
SEARCH_RESULT_LIMIT ||= 10 SEARCH_RESULT_LIMIT ||= 10
# @!method call(term:, guardian:) # @!method self.call(guardian:, params:)
# @param [String] term
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [String] :term
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -4,14 +4,15 @@ module Chat
# Service responsible for stopping streaming of a message. # Service responsible for stopping streaming of a message.
# #
# @example # @example
# Chat::StopMessageStreaming.call(message_id: 3, guardian: guardian) # Chat::StopMessageStreaming.call(params: { message_id: 3 }, guardian: guardian)
# #
class StopMessageStreaming class StopMessageStreaming
include ::Service::Base include ::Service::Base
# @!method call(message_id:, guardian:) # @!method self.call(guardian:, params:)
# @param [Integer] message_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Integer] :message_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do
attribute :message_id, :integer attribute :message_id, :integer

View File

@ -22,15 +22,16 @@ module Chat
# Only channels with threads enabled will return thread tracking state. # Only channels with threads enabled will return thread tracking state.
# #
# @example # @example
# Chat::TrackingState.call(channel_ids: [2, 3], thread_ids: [6, 7], guardian: guardian) # Chat::TrackingState.call(params: { channel_ids: [2, 3], thread_ids: [6, 7] }, guardian: guardian)
# #
class TrackingState class TrackingState
include Service::Base include Service::Base
# @!method call(thread_ids:, channel_ids:, guardian:) # @!method self.call(guardian:, params:)
# @param [Integer] thread_ids
# @param [Integer] channel_ids
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Integer] :thread_ids
# @option params [Integer] :channel_ids
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -5,14 +5,15 @@ module Chat
# Note the slug is modified to prevent collisions. # Note the slug is modified to prevent collisions.
# #
# @example # @example
# Chat::TrashChannel.call(channel_id: 2, guardian: guardian) # Chat::TrashChannel.call(params: { channel_id: 2 }, guardian: guardian)
# #
class TrashChannel class TrashChannel
include Service::Base include Service::Base
# @!method call(channel_id:, guardian:) # @!method self.call(guardian:, params:)
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
DELETE_CHANNEL_LOG_KEY = "chat_channel_delete" DELETE_CHANNEL_LOG_KEY = "chat_channel_delete"
@ -28,8 +29,8 @@ module Chat
private private
def fetch_channel(channel_id:) def fetch_channel(params:)
Chat::Channel.find_by(id: channel_id) Chat::Channel.find_by(id: params[:channel_id])
end end
def invalid_access(guardian:, channel:) def invalid_access(guardian:, channel:)

View File

@ -6,15 +6,16 @@ module Chat
# updated. # updated.
# #
# @example # @example
# Chat::TrashMessage.call(message_id: 2, channel_id: 1, guardian: guardian) # Chat::TrashMessage.call(params: { message_id: 2, channel_id: 1 }, guardian: guardian)
# #
class TrashMessage class TrashMessage
include Service::Base include Service::Base
# @!method call(message_id:, channel_id:, guardian:) # @!method self.call(guardian:, params:)
# @param [Integer] message_id
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Integer] :message_id
# @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -6,15 +6,16 @@ module Chat
# is updated. # is updated.
# #
# @example # @example
# Chat::TrashMessages.call(message_ids: [2, 3], channel_id: 1, guardian: guardian) # Chat::TrashMessages.call(params: { message_ids: [2, 3], channel_id: 1 }, guardian: guardian)
# #
class TrashMessages class TrashMessages
include Service::Base include Service::Base
# @!method call(message_ids:, channel_id:, guardian:) # @!method self.call(guardian:, params:)
# @param [Array<Integer>] message_ids
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Array<Integer>] :message_ids
# @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -6,17 +6,20 @@ module Chat
# @example # @example
# ::Chat::UnfollowChannel.call( # ::Chat::UnfollowChannel.call(
# guardian: guardian, # guardian: guardian,
# channel_id: 1, # params: {
# channel_id: 1,
# }
# ) # )
# #
class UnfollowChannel class UnfollowChannel
include Service::Base include Service::Base
# @!method call(guardian:, channel_id:,) # @!method self.call(guardian:, params:)
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Integer] channel_id of the channel # @param [Hash] params
# @option params [Integer] :channel_id ID of the channel
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do
attribute :channel_id, :integer attribute :channel_id, :integer

View File

@ -8,28 +8,30 @@ module Chat
# #
# @example # @example
# ::Chat::UpdateChannel.call( # ::Chat::UpdateChannel.call(
# channel_id: 2,
# guardian: guardian, # guardian: guardian,
# name: "SuperChannel", # params:{
# description: "This is the best channel", # channel_id: 2,
# slug: "super-channel", # name: "SuperChannel",
# threading_enabled: true, # description: "This is the best channel",
# slug: "super-channel",
# threading_enabled: true
# },
# ) # )
# #
class UpdateChannel class UpdateChannel
include Service::Base include Service::Base
# @!method call(channel_id:, guardian:, **params_to_edit) # @!method self.call(params:, guardian:)
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params_to_edit # @param [Hash] params
# @option params_to_edit [String,nil] name # @option params [Integer] :channel_id The channel ID
# @option params_to_edit [String,nil] description # @option params [String,nil] :name
# @option params_to_edit [String,nil] slug # @option params [String,nil] :description
# @option params_to_edit [Boolean] threading_enabled # @option params [String,nil] :slug
# @option params_to_edit [Boolean] auto_join_users Only valid for {CategoryChannel}. Whether active users # @option params [Boolean] :threading_enabled
# with permission to see the category should automatically join the channel. # @option params [Boolean] :auto_join_users Only valid for {CategoryChannel}. Whether active users with permission to see the category should automatically join the channel.
# @option params_to_edit [Boolean] allow_channel_wide_mentions Allow the use of @here and @all in the channel. # @option params [Boolean] :allow_channel_wide_mentions Allow the use of @here and @all in the channel.
# @return [Service::Base::Context] # @return [Service::Base::Context]
model :channel model :channel
@ -56,8 +58,8 @@ module Chat
private private
def fetch_channel(channel_id:) def fetch_channel(params:)
Chat::Channel.find_by(id: channel_id) Chat::Channel.find_by(id: params[:channel_id])
end end
def check_channel_permission(guardian:, channel:) def check_channel_permission(guardian:, channel:)

View File

@ -4,15 +4,16 @@ module Chat
# Service responsible for updating a chat channel status. # Service responsible for updating a chat channel status.
# #
# @example # @example
# Chat::UpdateChannelStatus.call(channel_id: 2, guardian: guardian, status: "open") # Chat::UpdateChannelStatus.call(guardian: guardian, params: { status: "open", channel_id: 2 })
# #
class UpdateChannelStatus class UpdateChannelStatus
include Service::Base include Service::Base
# @!method call(channel_id:, guardian:, status:) # @!method self.call(guardian:, params:)
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [String] status # @param [Hash] params
# @option params [Integer] :channel_id
# @option params [String] :status
# @return [Service::Base::Context] # @return [Service::Base::Context]
model :channel, :fetch_channel model :channel, :fetch_channel
@ -26,8 +27,8 @@ module Chat
private private
def fetch_channel(channel_id:) def fetch_channel(params:)
Chat::Channel.find_by(id: channel_id) Chat::Channel.find_by(id: params[:channel_id])
end end
def check_channel_permission(guardian:, channel:, contract:) def check_channel_permission(guardian:, channel:, contract:)

View File

@ -4,24 +4,32 @@ module Chat
# Service responsible for updating a message. # Service responsible for updating a message.
# #
# @example # @example
# Chat::UpdateMessage.call(message_id: 2, guardian: guardian, message: "A new message") # Chat::UpdateMessage.call(guardian: guardian, params: { message: "A new message", message_id: 2 })
# #
class UpdateMessage class UpdateMessage
include Service::Base include Service::Base
# @!method call(message_id:, guardian:, message:, upload_ids:) # @!method self.call(guardian:, params:, options:)
# @param guardian [Guardian] # @param guardian [Guardian]
# @param message_id [Integer] # @param [Hash] params
# @param message [String] # @option params [Integer] :message_id
# @param upload_ids [Array<Integer>] IDs of uploaded documents # @option params [String] :message
# @option params [Array<Integer>] :upload_ids IDs of uploaded documents
# @param [Hash] options
# @option options [Boolean] (true) :strip_whitespaces
# @option options [Boolean] :process_inline
# @return [Service::Base::Context]
options do
attribute :strip_whitespaces, :boolean, default: true
attribute :process_inline, :boolean, default: -> { Rails.env.test? }
end
contract do contract do
attribute :message_id, :string attribute :message_id, :string
attribute :message, :string attribute :message, :string
attribute :upload_ids, :array attribute :upload_ids, :array
attribute :streaming, :boolean, default: false
attribute :strip_whitespaces, :boolean, default: true
attribute :process_inline, :boolean, default: Rails.env.test?
validates :message_id, presence: true validates :message_id, presence: true
validates :message, presence: true, if: -> { upload_ids.blank? } validates :message, presence: true, if: -> { upload_ids.blank? }
@ -82,12 +90,12 @@ module Chat
guardian.can_edit_chat?(message) guardian.can_edit_chat?(message)
end end
def clean_message(contract:) def clean_message(contract:, options:)
contract.message = contract.message =
TextCleaner.clean( TextCleaner.clean(
contract.message, contract.message,
strip_whitespaces: contract.strip_whitespaces,
strip_zero_width_spaces: true, strip_zero_width_spaces: true,
strip_whitespaces: options.strip_whitespaces,
) )
end end
@ -149,14 +157,14 @@ module Chat
chars_edited > max_edited_chars chars_edited > max_edited_chars
end end
def publish(message:, guardian:, contract:) def publish(message:, guardian:, contract:, options:)
edit_timestamp = context[:revision]&.created_at&.iso8601(6) || Time.zone.now.iso8601(6) edit_timestamp = context[:revision]&.created_at&.iso8601(6) || Time.zone.now.iso8601(6)
::Chat::Publisher.publish_edit!(message.chat_channel, message) ::Chat::Publisher.publish_edit!(message.chat_channel, message)
DiscourseEvent.trigger(:chat_message_edited, message, message.chat_channel, message.user) DiscourseEvent.trigger(:chat_message_edited, message, message.chat_channel, message.user)
if contract.process_inline if options.process_inline
Jobs::Chat::ProcessMessage.new.execute( Jobs::Chat::ProcessMessage.new.execute(
{ chat_message_id: message.id, edit_timestamp: edit_timestamp }, { chat_message_id: message.id, edit_timestamp: edit_timestamp },
) )

View File

@ -7,16 +7,16 @@ module Chat
# Only the thread title can be updated. # Only the thread title can be updated.
# #
# @example # @example
# Chat::UpdateThread.call(thread_id: 88, guardian: guardian, title: "Restaurant for Saturday") # Chat::UpdateThread.call(guardian: guardian, params: { thread_id: 88, title: "Restaurant for Saturday" })
# #
class UpdateThread class UpdateThread
include Service::Base include Service::Base
# @!method call(thread_id:, channel_id:, guardian:, **params_to_edit) # @!method self.call(guardian:, params:)
# @param [Integer] thread_id
# @param [Integer] channel_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @option params_to_edit [String,nil] title # @param [Hash] params
# @option params [Integer] :thread_id
# @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -7,20 +7,23 @@ module Chat
# #
# @example # @example
# Chat::UpdateThreadNotificationSettings.call( # Chat::UpdateThreadNotificationSettings.call(
# thread_id: 88, # params: {
# channel_id: 2, # thread_id: 88,
# channel_id: 2,
# notification_level: notification_level,
# },
# guardian: guardian, # guardian: guardian,
# notification_level: notification_level,
# ) # )
# #
class UpdateThreadNotificationSettings class UpdateThreadNotificationSettings
include Service::Base include Service::Base
# @!method call(thread_id:, channel_id:, guardian:, notification_level:) # @!method self.call(guardian:, params:)
# @param [Integer] thread_id
# @param [Integer] channel_id
# @param [Integer] notification_level
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Integer] :thread_id
# @option params [Integer] :channel_id
# @option params [Integer] :notification_level
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -4,15 +4,16 @@ module Chat
# Service responsible for updating the last read message id of a membership. # Service responsible for updating the last read message id of a membership.
# #
# @example # @example
# Chat::UpdateUserChannelLastRead.call(channel_id: 2, message_id: 3, guardian: guardian) # Chat::UpdateUserChannelLastRead.call(params: { channel_id: 2, message_id: 3 }, guardian: guardian)
# #
class UpdateUserChannelLastRead class UpdateUserChannelLastRead
include ::Service::Base include ::Service::Base
# @!method call(channel_id:, message_id:, guardian:) # @!method self.call(guardian:, params:)
# @param [Integer] channel_id
# @param [Integer] message_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Integer] :channel_id
# @option params [Integer] :message_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -5,16 +5,17 @@ module Chat
# as read. # as read.
# #
# @example # @example
# Chat::UpdateUserThreadLastRead.call(channel_id: 2, thread_id: 3, message_id: 4, guardian: guardian) # Chat::UpdateUserThreadLastRead.call(params: { channel_id: 2, thread_id: 3, message_id: 4 }, guardian: guardian)
# #
class UpdateUserThreadLastRead class UpdateUserThreadLastRead
include ::Service::Base include ::Service::Base
# @!method call(channel_id:, thread_id:, guardian:) # @!method self.call(guardian:, params:)
# @param [Integer] channel_id
# @param [Integer] thread_id
# @param [Integer] message_id
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Hash] params
# @option params [Integer] :channel_id
# @option params [Integer] :thread_id
# @option params [Integer] :message_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do

View File

@ -6,20 +6,24 @@ module Chat
# @example # @example
# ::Chat::UpsertDraft.call( # ::Chat::UpsertDraft.call(
# guardian: guardian, # guardian: guardian,
# channel_id: 1, # params: {
# thread_id: 1, # channel_id: 1,
# data: { message: "foo" } # thread_id: 1,
# data: { message: "foo" }
# }
# ) # )
# #
class UpsertDraft class UpsertDraft
include Service::Base include Service::Base
# @!method call(guardian:, channel_id:, thread_id:, data:) # @!method self.call(guardian:, params:)
# @param [Guardian] guardian # @param [Guardian] guardian
# @param [Integer] channel_id of the channel # @param [Hash] params
# @param [String] json object as string containing the data of the draft (message, uploads, replyToMsg and editing keys) # @option params [Integer] :channel_id ID of the channel
# @option [Integer] thread_id of the channel # @option params [String] :data JSON object as string containing the data of the draft (message, uploads, replyToMsg and editing keys)
# @option params [Integer] :thread_id ID of the thread
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do contract do
attribute :channel_id, :integer attribute :channel_id, :integer
validates :channel_id, presence: true validates :channel_id, presence: true

View File

@ -246,10 +246,12 @@ module Chat
def self.tracking_state(channel_ids, guardian, include_threads: false) def self.tracking_state(channel_ids, guardian, include_threads: false)
Chat::TrackingState.call( Chat::TrackingState.call(
channel_ids: channel_ids, guardian:,
guardian: guardian, params: {
include_missing_memberships: true, include_missing_memberships: true,
include_threads: include_threads, channel_ids:,
include_threads:,
},
).report ).report
end end

View File

@ -236,16 +236,18 @@ module Chat
def add_moved_placeholder(destination_channel, first_moved_message) def add_moved_placeholder(destination_channel, first_moved_message)
@source_channel.add(Discourse.system_user) @source_channel.add(Discourse.system_user)
Chat::CreateMessage.call( Chat::CreateMessage.call(
chat_channel_id: @source_channel.id,
guardian: Discourse.system_user.guardian, guardian: Discourse.system_user.guardian,
message: params: {
I18n.t( chat_channel_id: @source_channel.id,
"chat.channel.messages_moved", message:
count: @source_message_ids.length, I18n.t(
acting_username: @acting_user.username, "chat.channel.messages_moved",
channel_name: destination_channel.title(@acting_user), count: @source_message_ids.length,
first_moved_message_url: first_moved_message.url, acting_username: @acting_user.username,
), channel_name: destination_channel.title(@acting_user),
first_moved_message_url: first_moved_message.url,
),
},
) )
end end

View File

@ -11,12 +11,19 @@ module ChatSDK
# @example Fetching messages from a channel with additional parameters # @example Fetching messages from a channel with additional parameters
# ChatSDK::Channel.messages(channel_id: 1, guardian: Guardian.new) # ChatSDK::Channel.messages(channel_id: 1, guardian: Guardian.new)
# #
def self.messages(channel_id:, guardian:, **params) def self.messages(...)
new.messages(channel_id:, guardian:, **params) new.messages(...)
end end
def messages(channel_id:, guardian:, **params) def messages(channel_id:, guardian:, **params)
Chat::ListChannelMessages.call(channel_id:, guardian:, **params, direction: "future") do Chat::ListChannelMessages.call(
guardian:,
params: {
channel_id:,
direction: "future",
**params,
},
) do
on_success { |messages:| messages } on_success { |messages:| messages }
on_failure { raise "Unexpected error" } on_failure { raise "Unexpected error" }
on_failed_policy(:can_view_channel) { raise "Guardian can't view channel" } on_failed_policy(:can_view_channel) { raise "Guardian can't view channel" }

View File

@ -24,8 +24,8 @@ module ChatSDK
# ChatSDK::Message.create_with_stream(raw: "Streaming message", channel_id: 1, guardian: Guardian.new) do |helper, message| # ChatSDK::Message.create_with_stream(raw: "Streaming message", channel_id: 1, guardian: Guardian.new) do |helper, message|
# helper.stream(raw: "Continuation of the message") # helper.stream(raw: "Continuation of the message")
# end # end
def self.create(**params, &block) def self.create(...)
new.create(**params, &block) new.create(...)
end end
# Creates a new message with streaming enabled by default. # Creates a new message with streaming enabled by default.
@ -46,8 +46,8 @@ module ChatSDK
# @return [Chat::Message] The message object. # @return [Chat::Message] The message object.
# @example Streaming a message # @example Streaming a message
# ChatSDK::Message.stream(message_id: 42, guardian: guardian, raw: "text") # ChatSDK::Message.stream(message_id: 42, guardian: guardian, raw: "text")
def self.stream(raw:, message_id:, guardian:, &block) def self.stream(...)
new.stream(raw: raw, message_id: message_id, guardian: guardian, &block) new.stream(...)
end end
# Starts streaming for a specific chat message. # Starts streaming for a specific chat message.
@ -57,8 +57,8 @@ module ChatSDK
# @return [Chat::Message] The message object. # @return [Chat::Message] The message object.
# @example Starting the streaming of a message # @example Starting the streaming of a message
# ChatSDK::Message.start_stream(message_id: 42, guardian: guardian) # ChatSDK::Message.start_stream(message_id: 42, guardian: guardian)
def self.start_stream(message_id:, guardian:) def self.start_stream(...)
new.start_stream(message_id: message_id, guardian: guardian) new.start_stream(...)
end end
# Stops streaming for a specific chat message. # Stops streaming for a specific chat message.
@ -68,8 +68,8 @@ module ChatSDK
# @return [Chat::Message] The message object. # @return [Chat::Message] The message object.
# @example Stopping the streaming of a message # @example Stopping the streaming of a message
# ChatSDK::Message.stop_stream(message_id: 42, guardian: guardian) # ChatSDK::Message.stop_stream(message_id: 42, guardian: guardian)
def self.stop_stream(message_id:, guardian:) def self.stop_stream(...)
new.stop_stream(message_id: message_id, guardian: guardian) new.stop_stream(...)
end end
def start_stream(message_id:, guardian:) def start_stream(message_id:, guardian:)
@ -89,7 +89,7 @@ module ChatSDK
end end
def stop_stream(message_id:, guardian:) def stop_stream(message_id:, guardian:)
Chat::StopMessageStreaming.call(message_id:, guardian:) do Chat::StopMessageStreaming.call(params: { message_id: }, guardian:) do
on_success { |message:| message } on_success { |message:| message }
on_model_not_found(:message) { raise "Couldn't find message with id: `#{message_id}`" } on_model_not_found(:message) { raise "Couldn't find message with id: `#{message_id}`" }
on_model_not_found(:membership) do on_model_not_found(:membership) do
@ -121,18 +121,22 @@ module ChatSDK
) )
message = message =
Chat::CreateMessage.call( Chat::CreateMessage.call(
message: raw, params: {
guardian: guardian, message: raw,
chat_channel_id: channel_id, chat_channel_id: channel_id,
in_reply_to_id: in_reply_to_id, in_reply_to_id:,
thread_id: thread_id, thread_id:,
upload_ids: upload_ids, upload_ids:,
streaming: streaming, **params,
enforce_membership: enforce_membership, },
force_thread: force_thread, options: {
strip_whitespaces: strip_whitespaces, created_by_sdk: true,
created_by_sdk: true, streaming:,
**params, enforce_membership:,
force_thread:,
strip_whitespaces:,
},
guardian:,
) do ) do
on_model_not_found(:channel) { raise "Couldn't find channel with id: `#{channel_id}`" } on_model_not_found(:channel) { raise "Couldn't find channel with id: `#{channel_id}`" }
on_model_not_found(:membership) do on_model_not_found(:membership) do
@ -176,11 +180,14 @@ module ChatSDK
return false if !message.streaming || !raw return false if !message.streaming || !raw
Chat::UpdateMessage.call( Chat::UpdateMessage.call(
message_id: message.id,
message: message.message + raw,
guardian: guardian, guardian: guardian,
streaming: true, params: {
strip_whitespaces: false, message_id: message.id,
message: message.message + raw,
},
options: {
strip_whitespaces: false,
},
) { on_failure { raise "Unexpected error" } } ) { on_failure { raise "Unexpected error" } }
message message

View File

@ -13,7 +13,7 @@ module ChatSDK
# ChatSDK::Thread.update_title(title: "New Thread Title", thread_id: 1, guardian: Guardian.new) # ChatSDK::Thread.update_title(title: "New Thread Title", thread_id: 1, guardian: Guardian.new)
# #
def self.update_title(thread_id:, guardian:, title:) def self.update_title(thread_id:, guardian:, title:)
new.update(title: title, thread_id: thread_id, guardian: guardian) new.update(thread_id:, guardian:, title:)
end end
# Retrieves messages from a specified thread. # Retrieves messages from a specified thread.
@ -25,8 +25,8 @@ module ChatSDK
# @example Fetching messages from a thread with additional parameters # @example Fetching messages from a thread with additional parameters
# ChatSDK::Thread.messages(thread_id: 1, guardian: Guardian.new) # ChatSDK::Thread.messages(thread_id: 1, guardian: Guardian.new)
# #
def self.messages(thread_id:, guardian:, **params) def self.messages(...)
new.messages(thread_id: thread_id, guardian: guardian, **params) new.messages(...)
end end
# Fetches the first messages from a specified chat thread, starting from the first available message. # Fetches the first messages from a specified chat thread, starting from the first available message.
@ -41,9 +41,9 @@ module ChatSDK
# #
def self.first_messages(thread_id:, guardian:, page_size: 10) def self.first_messages(thread_id:, guardian:, page_size: 10)
new.messages( new.messages(
thread_id: thread_id, thread_id:,
guardian: guardian, guardian:,
page_size: page_size, page_size:,
direction: "future", direction: "future",
fetch_from_first_message: true, fetch_from_first_message: true,
) )
@ -61,20 +61,27 @@ module ChatSDK
# #
def self.last_messages(thread_id:, guardian:, page_size: 10) def self.last_messages(thread_id:, guardian:, page_size: 10)
new.messages( new.messages(
thread_id: thread_id, thread_id:,
guardian: guardian, guardian:,
page_size: page_size, page_size:,
direction: "past", direction: "past",
fetch_from_last_message: true, fetch_from_last_message: true,
) )
end end
def self.update(**params) def self.update(...)
new.update(**params) new.update(...)
end end
def messages(thread_id:, guardian:, direction: "future", **params) def messages(thread_id:, guardian:, direction: "future", **params)
Chat::ListChannelThreadMessages.call(thread_id:, guardian:, direction:, **params) do Chat::ListChannelThreadMessages.call(
guardian:,
params: {
thread_id:,
direction:,
**params,
},
) do
on_success { |messages:| messages } on_success { |messages:| messages }
on_failed_policy(:can_view_thread) { raise "Guardian can't view thread" } on_failed_policy(:can_view_thread) { raise "Guardian can't view thread" }
on_failed_policy(:target_message_exists) { raise "Target message doesn't exist" } on_failed_policy(:target_message_exists) { raise "Target message doesn't exist" }
@ -82,8 +89,8 @@ module ChatSDK
end end
end end
def update(**params) def update(guardian:, **params)
Chat::UpdateThread.call(params) do Chat::UpdateThread.call(guardian:, params:) do
on_model_not_found(:channel) do on_model_not_found(:channel) do
raise "Couldnt find channel with id: `#{params[:channel_id]}`" raise "Couldnt find channel with id: `#{params[:channel_id]}`"
end end

View File

@ -472,9 +472,11 @@ after_initialize do
creator = creator =
::Chat::CreateMessage.call( ::Chat::CreateMessage.call(
chat_channel_id: channel.id,
guardian: sender.guardian, guardian: sender.guardian,
message: utils.apply_placeholders(fields.dig("message", "value"), placeholders), params: {
chat_channel_id: channel.id,
message: utils.apply_placeholders(fields.dig("message", "value"), placeholders),
},
) )
if creator.failure? if creator.failure?

View File

@ -93,16 +93,20 @@ Fabricator(:chat_message_with_service, class_name: "Chat::CreateMessage") do
result = result =
resolved_class.call( resolved_class.call(
chat_channel_id: channel.id, params: {
chat_channel_id: channel.id,
message:
transients[:message] ||
Faker::Alphanumeric.alpha(number: SiteSetting.chat_minimum_message_length),
thread_id: transients[:thread]&.id,
in_reply_to_id: transients[:in_reply_to]&.id,
upload_ids: transients[:upload_ids],
},
options: {
process_inline: true,
},
guardian: user.guardian, guardian: user.guardian,
message:
transients[:message] ||
Faker::Alphanumeric.alpha(number: SiteSetting.chat_minimum_message_length),
thread_id: transients[:thread]&.id,
in_reply_to_id: transients[:in_reply_to]&.id,
upload_ids: transients[:upload_ids],
incoming_chat_webhook: transients[:incoming_chat_webhook], incoming_chat_webhook: transients[:incoming_chat_webhook],
process_inline: true,
) )
if result.failure? if result.failure?

View File

@ -24,10 +24,12 @@ RSpec.describe "Chat::Thread replies_count cache accuracy" do
# Create 5 replies # Create 5 replies
5.times do |i| 5.times do |i|
Chat::CreateMessage.call( Chat::CreateMessage.call(
chat_channel_id: thread.channel_id,
guardian: guardian, guardian: guardian,
thread_id: thread.id, params: {
message: "Hello world #{i}", chat_channel_id: thread.channel_id,
thread_id: thread.id,
message: "Hello world #{i}",
},
) )
end end
@ -39,10 +41,12 @@ RSpec.describe "Chat::Thread replies_count cache accuracy" do
# Travel to the future so the cache expires. # Travel to the future so the cache expires.
travel_to 6.minutes.from_now travel_to 6.minutes.from_now
Chat::CreateMessage.call( Chat::CreateMessage.call(
chat_channel_id: thread.channel_id,
guardian: guardian, guardian: guardian,
thread_id: thread.id, params: {
message: "Hello world now that time has passed", chat_channel_id: thread.channel_id,
thread_id: thread.id,
message: "Hello world now that time has passed",
},
) )
expect(thread.replies_count_cache).to eq(6) expect(thread.replies_count_cache).to eq(6)
expect(thread.reload.replies_count).to eq(6) expect(thread.reload.replies_count).to eq(6)

View File

@ -15,7 +15,9 @@ describe Jobs::Chat::NotifyMentioned do
result = result =
Chat::CreateDirectMessageChannel.call( Chat::CreateDirectMessageChannel.call(
guardian: user_1.guardian, guardian: user_1.guardian,
target_usernames: [user_1.username, user_2.username], params: {
target_usernames: [user_1.username, user_2.username],
},
) )
service_failed!(result) if result.failure? service_failed!(result) if result.failure?

View File

@ -145,8 +145,10 @@ describe Chat::Notifier do
Chat::UpdateMessage.call( Chat::UpdateMessage.call(
guardian: user_1.guardian, guardian: user_1.guardian,
message_id: msg.id, params: {
message: "hello @all", message_id: msg.id,
message: "hello @all",
},
) )
described_class.new(msg, msg.created_at).notify_edit described_class.new(msg, msg.created_at).notify_edit
@ -425,7 +427,9 @@ describe Chat::Notifier do
result = result =
Chat::CreateDirectMessageChannel.call( Chat::CreateDirectMessageChannel.call(
guardian: user_1.guardian, guardian: user_1.guardian,
target_usernames: [user_1.username, user_2.username], params: {
target_usernames: [user_1.username, user_2.username],
},
) )
service_failed!(result) if result.failure? service_failed!(result) if result.failure?
result.channel result.channel

View File

@ -115,8 +115,10 @@ describe Chat::ReviewQueue do
it "ignores the cooldown window when the message is edited" do it "ignores the cooldown window when the message is edited" do
Chat::UpdateMessage.call( Chat::UpdateMessage.call(
guardian: Guardian.new(message.user), guardian: Guardian.new(message.user),
message_id: message.id, params: {
message: "I'm editing this message. Please flag it.", message_id: message.id,
message: "I'm editing this message. Please flag it.",
},
) )
expect(second_flag_result).to include success: true expect(second_flag_result).to include success: true

View File

@ -38,11 +38,13 @@ module ChatSystemHelpers
last_user = ((users - [last_user]).presence || users).sample last_user = ((users - [last_user]).presence || users).sample
creator = creator =
Chat::CreateMessage.call( Chat::CreateMessage.call(
chat_channel_id: channel.id,
in_reply_to_id: in_reply_to,
thread_id: thread_id,
guardian: last_user.guardian, guardian: last_user.guardian,
message: Faker::Alphanumeric.alpha(number: SiteSetting.chat_minimum_message_length), params: {
chat_channel_id: channel.id,
in_reply_to_id: in_reply_to,
thread_id: thread_id,
message: Faker::Alphanumeric.alpha(number: SiteSetting.chat_minimum_message_length),
},
) )
raise "#{creator.inspect_steps.inspect}\n\n#{creator.inspect_steps.error}" if creator.failure? raise "#{creator.inspect_steps.inspect}\n\n#{creator.inspect_steps.error}" if creator.failure?
@ -69,10 +71,14 @@ module ChatSpecHelpers
def update_message!(message, text: nil, user: Discourse.system_user, upload_ids: nil) def update_message!(message, text: nil, user: Discourse.system_user, upload_ids: nil)
Chat::UpdateMessage.call( Chat::UpdateMessage.call(
guardian: user.guardian, guardian: user.guardian,
message_id: message.id, params: {
upload_ids: upload_ids, message_id: message.id,
message: text, upload_ids: upload_ids,
process_inline: true, message: text,
},
options: {
process_inline: true,
},
) do |result| ) do |result|
on_success { result.message_instance } on_success { result.message_instance }
on_failure { service_failed!(result) } on_failure { service_failed!(result) }
@ -81,8 +87,10 @@ module ChatSpecHelpers
def trash_message!(message, user: Discourse.system_user) def trash_message!(message, user: Discourse.system_user)
Chat::TrashMessage.call( Chat::TrashMessage.call(
message_id: message.id, params: {
channel_id: message.chat_channel_id, message_id: message.id,
channel_id: message.chat_channel_id,
},
guardian: user.guardian, guardian: user.guardian,
) do |result| ) do |result|
on_success { result } on_success { result }
@ -92,8 +100,10 @@ module ChatSpecHelpers
def restore_message!(message, user: Discourse.system_user) def restore_message!(message, user: Discourse.system_user)
Chat::RestoreMessage.call( Chat::RestoreMessage.call(
message_id: message.id, params: {
channel_id: message.chat_channel_id, message_id: message.id,
channel_id: message.chat_channel_id,
},
guardian: user.guardian, guardian: user.guardian,
) do |result| ) do |result|
on_success { result } on_success { result }
@ -104,8 +114,10 @@ module ChatSpecHelpers
def add_users_to_channel(users, channel, user: Discourse.system_user) def add_users_to_channel(users, channel, user: Discourse.system_user)
::Chat::AddUsersToChannel.call( ::Chat::AddUsersToChannel.call(
guardian: user.guardian, guardian: user.guardian,
channel_id: channel.id, params: {
usernames: Array(users).map(&:username), channel_id: channel.id,
usernames: Array(users).map(&:username),
},
) do |result| ) do |result|
on_success { result } on_success { result }
on_failure { service_failed!(result) } on_failure { service_failed!(result) }
@ -121,9 +133,11 @@ module ChatSpecHelpers
::Chat::UpsertDraft.call( ::Chat::UpsertDraft.call(
guardian: user.guardian, guardian: user.guardian,
channel_id: channel.id, params: {
thread_id: thread&.id, channel_id: channel.id,
data: data.to_json, thread_id: thread&.id,
data: data.to_json,
},
) do |result| ) do |result|
on_success { result } on_success { result }
on_failure { service_failed!(result) } on_failure { service_failed!(result) }

View File

@ -2,7 +2,7 @@
RSpec.describe Chat::AutoRemove::HandleCategoryUpdated do RSpec.describe Chat::AutoRemove::HandleCategoryUpdated do
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:) }
let(:params) { { category_id: updated_category.id } } let(:params) { { category_id: updated_category.id } }

View File

@ -2,9 +2,9 @@
RSpec.describe Chat::AutoRemove::HandleChatAllowedGroupsChange do RSpec.describe Chat::AutoRemove::HandleChatAllowedGroupsChange do
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:) }
let(:params) { { new_allowed_groups: new_allowed_groups } } let(:params) { { new_allowed_groups: } }
fab!(:user_1) { Fabricate(:user, refresh_auto_groups: true) } fab!(:user_1) { Fabricate(:user, refresh_auto_groups: true) }
fab!(:user_2) { Fabricate(:user, refresh_auto_groups: true) } fab!(:user_2) { Fabricate(:user, refresh_auto_groups: true) }
fab!(:admin_1) { Fabricate(:admin) } fab!(:admin_1) { Fabricate(:admin) }

View File

@ -6,7 +6,7 @@ RSpec.describe Chat::AutoRemove::HandleDestroyedGroup do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:) }
let(:params) { { destroyed_group_user_ids: [admin_1.id, admin_2.id, user_1.id, user_2.id] } } let(:params) { { destroyed_group_user_ids: [admin_1.id, admin_2.id, user_1.id, user_2.id] } }
fab!(:user_1) { Fabricate(:user, refresh_auto_groups: true) } fab!(:user_1) { Fabricate(:user, refresh_auto_groups: true) }

View File

@ -2,7 +2,7 @@
RSpec.describe Chat::AutoRemove::HandleUserRemovedFromGroup do RSpec.describe Chat::AutoRemove::HandleUserRemovedFromGroup do
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:) }
let(:params) { { user_id: removed_user.id } } let(:params) { { user_id: removed_user.id } }
fab!(:removed_user) { Fabricate(:user) } fab!(:removed_user) { Fabricate(:user) }

View File

@ -10,7 +10,7 @@ RSpec.describe Chat::AddUsersToChannel do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:users) { Fabricate.times(5, :user) } fab!(:users) { Fabricate.times(5, :user) }
@ -21,9 +21,8 @@ RSpec.describe Chat::AddUsersToChannel do
fab!(:group) { Fabricate(:public_group, users: [group_user_1, group_user_2]) } fab!(:group) { Fabricate(:public_group, users: [group_user_1, group_user_2]) }
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:params) do let(:params) { { channel_id: channel.id, usernames: users.map(&:username) } }
{ guardian: guardian, channel_id: channel.id, usernames: users.map(&:username) } let(:dependencies) { { guardian: } }
end
context "when all steps pass" do context "when all steps pass" do
before { channel.add(current_user) } before { channel.add(current_user) }

View File

@ -45,7 +45,7 @@ describe Chat::AutoJoinChannelBatch do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:) }
fab!(:channel) { Fabricate(:chat_channel, auto_join_users: true) } fab!(:channel) { Fabricate(:chat_channel, auto_join_users: true) }

View File

@ -1,35 +1,21 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::CreateCategoryChannel do RSpec.describe Chat::CreateCategoryChannel do
describe Chat::CreateCategoryChannel::Contract, type: :model do describe described_class::Contract, type: :model do
it { is_expected.to validate_presence_of :category_id } it { is_expected.to validate_presence_of :category_id }
it { is_expected.to validate_length_of(:name).is_at_most(SiteSetting.max_topic_title_length) } it { is_expected.to validate_length_of(:name).is_at_most(SiteSetting.max_topic_title_length) }
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:admin) } fab!(:current_user) { Fabricate(:admin) }
fab!(:category) fab!(:category)
let(:category_id) { category.id } let(:category_id) { category.id }
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:params) { { guardian: guardian, category_id: category_id, name: "cool channel" } } let(:params) { { category_id:, name: "cool channel" } }
let(:dependencies) { { guardian: } }
it "can create several channels with empty slugs" do
SiteSetting.slug_generation_method = "none"
expect do
described_class.call(params.merge(name: "channel 1", slug: nil))
end.not_to raise_error
expect do
described_class.call(params.merge(name: "channel 2", slug: nil))
end.not_to raise_error
end
it "can create several channels with unicode names" do
expect do described_class.call(params.merge(name: "マイキ")) end.not_to raise_error
expect do described_class.call(params.merge(name: "境界")) end.not_to raise_error
end
context "when public channels are disabled" do context "when public channels are disabled" do
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }

View File

@ -29,7 +29,7 @@ RSpec.describe Chat::CreateDirectMessageChannel do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user, username: "guybrush", refresh_auto_groups: true) } fab!(:current_user) { Fabricate(:user, username: "guybrush", refresh_auto_groups: true) }
fab!(:user_1) { Fabricate(:user, username: "lechuck") } fab!(:user_1) { Fabricate(:user, username: "lechuck") }
@ -40,7 +40,8 @@ RSpec.describe Chat::CreateDirectMessageChannel do
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:target_usernames) { [user_1.username, user_2.username] } let(:target_usernames) { [user_1.username, user_2.username] }
let(:name) { "" } let(:name) { "" }
let(:params) { { guardian: guardian, target_usernames: target_usernames, name: name } } let(:params) { { target_usernames:, name: } }
let(:dependencies) { { guardian: } }
context "when all steps pass" do context "when all steps pass" do
it { is_expected.to run_successfully } it { is_expected.to run_successfully }
@ -117,7 +118,7 @@ RSpec.describe Chat::CreateDirectMessageChannel do
let(:name) { "Monkey Island" } let(:name) { "Monkey Island" }
it "creates a second channel" do it "creates a second channel" do
described_class.call(params) described_class.call(params:, **dependencies)
expect { result }.to change { Chat::Channel.count }.and change { expect { result }.to change { Chat::Channel.count }.and change {
Chat::DirectMessage.count Chat::DirectMessage.count
@ -129,7 +130,7 @@ RSpec.describe Chat::CreateDirectMessageChannel do
let(:target_usernames) { [user_1.username, user_2.username] } let(:target_usernames) { [user_1.username, user_2.username] }
it "creates a second channel" do it "creates a second channel" do
described_class.call(params) described_class.call(params:, **dependencies)
expect { result }.to change { Chat::Channel.count }.and change { expect { result }.to change { Chat::Channel.count }.and change {
Chat::DirectMessage.count Chat::DirectMessage.count
@ -141,7 +142,7 @@ RSpec.describe Chat::CreateDirectMessageChannel do
let(:target_usernames) { [user_1.username] } let(:target_usernames) { [user_1.username] }
it "reuses the existing channel" do it "reuses the existing channel" do
existing_channel = described_class.call(params).channel existing_channel = described_class.call(params:, **dependencies).channel
expect(result.channel.id).to eq(existing_channel.id) expect(result.channel.id).to eq(existing_channel.id)
end end
@ -151,8 +152,9 @@ RSpec.describe Chat::CreateDirectMessageChannel do
let(:target_usernames) { [user_1.username] } let(:target_usernames) { [user_1.username] }
it "returns the non group existing channel" do it "returns the non group existing channel" do
group_channel = described_class.call(params.merge(name: "cats")).channel group_channel =
channel = described_class.call(params).channel described_class.call(params: params.merge(name: "cats"), **dependencies).channel
channel = described_class.call(params:, **dependencies).channel
expect(result.channel.id).to_not eq(group_channel.id) expect(result.channel.id).to_not eq(group_channel.id)
expect(result.channel.id).to eq(channel.id) expect(result.channel.id).to eq(channel.id)

View File

@ -20,7 +20,7 @@ RSpec.describe Chat::CreateMessage do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, options:, **dependencies) }
fab!(:user) fab!(:user)
fab!(:other_user) { Fabricate(:user) } fab!(:other_user) { Fabricate(:user) }
@ -35,16 +35,15 @@ RSpec.describe Chat::CreateMessage do
let(:context_post_ids) { nil } let(:context_post_ids) { nil }
let(:params) do let(:params) do
{ {
enforce_membership: false,
guardian: guardian,
chat_channel_id: channel.id, chat_channel_id: channel.id,
message: content, message: content,
upload_ids: [upload.id], upload_ids: [upload.id],
context_topic_id: context_topic_id, context_topic_id: context_topic_id,
context_post_ids: context_post_ids, context_post_ids: context_post_ids,
force_thread: false,
} }
end end
let(:options) { { enforce_membership: false, force_thread: false } }
let(:dependencies) { { guardian: } }
let(:message) { result[:message_instance].reload } let(:message) { result[:message_instance].reload }
before { channel.add(guardian.user) } before { channel.add(guardian.user) }
@ -74,9 +73,12 @@ RSpec.describe Chat::CreateMessage do
end end
context "when strip_whitespace is disabled" do context "when strip_whitespace is disabled" do
it "doesn't strip newlines" do before do
params[:strip_whitespaces] = false options[:strip_whitespaces] = false
params[:message] = "aaaaaaa\n" params[:message] = "aaaaaaa\n"
end
it "doesn't strip newlines" do
expect(message.message).to eq("aaaaaaa\n") expect(message.message).to eq("aaaaaaa\n")
end end
end end
@ -84,7 +86,7 @@ RSpec.describe Chat::CreateMessage do
context "when coming from a webhook" do context "when coming from a webhook" do
let(:incoming_webhook) { Fabricate(:incoming_chat_webhook, chat_channel: channel) } let(:incoming_webhook) { Fabricate(:incoming_chat_webhook, chat_channel: channel) }
before { params[:incoming_chat_webhook] = incoming_webhook } before { dependencies[:incoming_chat_webhook] = incoming_webhook }
it "creates a webhook event" do it "creates a webhook event" do
expect { result }.to change { Chat::WebhookEvent.count }.by(1) expect { result }.to change { Chat::WebhookEvent.count }.by(1)
@ -104,15 +106,21 @@ RSpec.describe Chat::CreateMessage do
result result
end end
it "can enqueue a job to process message" do context "when process_inline is false" do
params[:process_inline] = false before { options[:process_inline] = false }
expect_enqueued_with(job: Jobs::Chat::ProcessMessage) { result }
it "enqueues a job to process message" do
expect_enqueued_with(job: Jobs::Chat::ProcessMessage) { result }
end
end end
it "can process a message inline" do context "when process_inline is true" do
params[:process_inline] = true before { options[:process_inline] = true }
Jobs::Chat::ProcessMessage.any_instance.expects(:execute).once
expect_not_enqueued_with(job: Jobs::Chat::ProcessMessage) { result } it "processes a message inline" do
Jobs::Chat::ProcessMessage.any_instance.expects(:execute).once
expect_not_enqueued_with(job: Jobs::Chat::ProcessMessage) { result }
end
end end
it "triggers a Discourse event" do it "triggers a Discourse event" do
@ -127,11 +135,11 @@ RSpec.describe Chat::CreateMessage do
result result
end end
context "when context given" do context "when a context is given" do
let(:context_post_ids) { [1, 2] } let(:context_post_ids) { [1, 2] }
let(:context_topic_id) { 3 } let(:context_topic_id) { 3 }
it "triggers a Discourse event with context if given" do it "triggers a Discourse event with context" do
DiscourseEvent.expects(:trigger).with( DiscourseEvent.expects(:trigger).with(
:chat_message_created, :chat_message_created,
instance_of(Chat::Message), instance_of(Chat::Message),
@ -245,7 +253,7 @@ RSpec.describe Chat::CreateMessage do
before do before do
SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone] SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone]
params[:enforce_membership] = true options[:enforce_membership] = true
end end
it { is_expected.to run_successfully } it { is_expected.to run_successfully }
@ -345,7 +353,7 @@ RSpec.describe Chat::CreateMessage do
end end
context "when thread is forced" do context "when thread is forced" do
before { params[:force_thread] = true } before { options[:force_thread] = true }
it "publishes the new thread" do it "publishes the new thread" do
Chat::Publisher.expects(:publish_thread_created!).with( Chat::Publisher.expects(:publish_thread_created!).with(

View File

@ -8,7 +8,7 @@ RSpec.describe Chat::CreateThread do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel, threading_enabled: true) } fab!(:channel_1) { Fabricate(:chat_channel, threading_enabled: true) }
@ -16,14 +16,8 @@ RSpec.describe Chat::CreateThread do
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:title) { nil } let(:title) { nil }
let(:params) do let(:params) { { original_message_id: message_1.id, channel_id: channel_1.id, title: } }
{ let(:dependencies) { { guardian: } }
guardian: guardian,
original_message_id: message_1.id,
channel_id: channel_1.id,
title: title,
}
end
context "when all steps pass" do context "when all steps pass" do
it { is_expected.to run_successfully } it { is_expected.to run_successfully }
@ -101,8 +95,10 @@ RSpec.describe Chat::CreateThread do
before do before do
Chat::CreateThread.call( Chat::CreateThread.call(
guardian: current_user.guardian, guardian: current_user.guardian,
original_message_id: message_1.id, params: {
channel_id: channel_1.id, original_message_id: message_1.id,
channel_id: channel_1.id,
},
) )
end end

View File

@ -11,7 +11,7 @@ RSpec.describe Chat::FlagMessage do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel) } fab!(:channel_1) { Fabricate(:chat_channel) }
@ -26,7 +26,6 @@ RSpec.describe Chat::FlagMessage do
let(:take_action) { nil } let(:take_action) { nil }
let(:params) do let(:params) do
{ {
guardian: guardian,
channel_id: channel_id, channel_id: channel_id,
message_id:, message_id:,
flag_type_id: flag_type_id, flag_type_id: flag_type_id,
@ -35,6 +34,7 @@ RSpec.describe Chat::FlagMessage do
take_action: take_action, take_action: take_action,
} }
end end
let(:dependencies) { { guardian: } }
before { SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:everyone] } before { SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:everyone] }

View File

@ -7,7 +7,7 @@ RSpec.describe Chat::InviteUsersToChannel do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(**params, **dependencies) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:admin) } fab!(:current_user) { Fabricate(:admin) }
fab!(:user_1) { Fabricate(:user) } fab!(:user_1) { Fabricate(:user) }

View File

@ -2,24 +2,22 @@
RSpec.describe Chat::LeaveChannel do RSpec.describe Chat::LeaveChannel do
describe described_class::Contract, type: :model do describe described_class::Contract, type: :model do
subject(:contract) { described_class.new }
it { is_expected.to validate_presence_of(:channel_id) } it { is_expected.to validate_presence_of(:channel_id) }
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:channel_1) { Fabricate(:chat_channel) } fab!(:channel_1) { Fabricate(:chat_channel) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:channel_id) { channel_1.id } let(:channel_id) { channel_1.id }
let(:params) { { channel_id: } }
let(:dependencies) { { guardian: } }
before { SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:everyone] } before { SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:everyone] }
let(:params) { { guardian: guardian, channel_id: channel_id } }
context "when all steps pass" do context "when all steps pass" do
context "when category channel" do context "when category channel" do
context "with existing membership" do context "with existing membership" do

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::ListChannelMessages do RSpec.describe Chat::ListChannelMessages do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:user) fab!(:user)
fab!(:channel) { Fabricate(:chat_channel) } fab!(:channel) { Fabricate(:chat_channel) }
@ -9,7 +9,8 @@ RSpec.describe Chat::ListChannelMessages do
let(:guardian) { Guardian.new(user) } let(:guardian) { Guardian.new(user) }
let(:channel_id) { channel.id } let(:channel_id) { channel.id }
let(:optional_params) { {} } let(:optional_params) { {} }
let(:params) { { guardian: guardian, channel_id: channel_id }.merge(optional_params) } let(:params) { { channel_id: }.merge(optional_params) }
let(:dependencies) { { guardian: } }
before { channel.add(user) } before { channel.add(user) }

View File

@ -1,168 +1,184 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::ListChannelThreadMessages do RSpec.describe Chat::ListChannelThreadMessages do
subject(:result) { described_class.call(params) } describe described_class::Contract, type: :model do
it { is_expected.to validate_presence_of(:thread_id) }
fab!(:user) it do
fab!(:thread) { Fabricate(:chat_thread, channel: Fabricate(:chat_channel)) } is_expected.to validate_inclusion_of(:direction).in_array(
Chat::MessagesQuery::VALID_DIRECTIONS,
let(:guardian) { Guardian.new(user) } ).allow_nil
let(:thread_id) { thread.id }
let(:optional_params) { {} }
let(:params) { { guardian: guardian, thread_id: thread_id }.merge(optional_params) }
before { thread.channel.add(user) }
context "when contract" do
context "when thread_id is not present" do
let(:thread_id) { nil }
it { is_expected.to fail_a_contract }
end end
it do
is_expected.to allow_values(Chat::MessagesQuery::MAX_PAGE_SIZE, 1, "1", nil).for(:page_size)
end
it { is_expected.not_to allow_values(Chat::MessagesQuery::MAX_PAGE_SIZE + 1).for(:page_size) }
end end
context "when fetch_thread" do describe ".call" do
context "when thread doesnt exist" do subject(:result) { described_class.call(params:, **dependencies) }
let(:thread_id) { -1 }
it { is_expected.to fail_to_find_a_model(:thread) } fab!(:user)
end fab!(:thread) { Fabricate(:chat_thread, channel: Fabricate(:chat_channel)) }
context "when thread exists" do let(:guardian) { Guardian.new(user) }
it { is_expected.to run_successfully } let(:thread_id) { thread.id }
let(:optional_params) { {} }
let(:params) { { thread_id: }.merge(optional_params) }
let(:dependencies) { { guardian: } }
it "finds the correct channel" do before { thread.channel.add(user) }
expect(result.thread).to eq(thread)
context "when contract" do
context "when thread_id is not present" do
let(:thread_id) { nil }
it { is_expected.to fail_a_contract }
end end
end end
end
context "when can_view_thread" do context "when fetch_thread" do
context "when channel is private" do context "when thread doesnt exist" do
fab!(:thread) { Fabricate(:chat_thread, channel: Fabricate(:private_category_channel)) } let(:thread_id) { -1 }
it { is_expected.to fail_a_policy(:can_view_thread) } it { is_expected.to fail_to_find_a_model(:thread) }
end
context "with system user" do context "when thread exists" do
fab!(:user) { Discourse.system_user } it { is_expected.to run_successfully }
it "finds the correct channel" do
expect(result.thread).to eq(thread)
end
end
end
context "when can_view_thread" do
context "when channel is private" do
fab!(:thread) { Fabricate(:chat_thread, channel: Fabricate(:private_category_channel)) }
it { is_expected.to fail_a_policy(:can_view_thread) }
context "with system user" do
fab!(:user) { Discourse.system_user }
it { is_expected.to run_successfully }
end
end
end
context "when determine_target_message_id" do
let(:optional_params) { { fetch_from_last_message: true } }
context "when fetch_from_last_message is true" do
it "sets target_message_id to last thread message id" do
expect(result.target_message_id).to eq(thread.chat_messages.last.id)
end
end
context "when fetch_from_first_message is true" do
it "sets target_message_id to first thread message id" do
expect(result.target_message_id).to eq(thread.chat_messages.first.id)
end
end
context "when fetch_from_last_read is true" do
let(:optional_params) { { fetch_from_last_read: true } }
before do
thread.add(user)
thread.membership_for(guardian.user).update!(last_read_message_id: 1)
end
it "sets target_message_id to last_read_message_id" do
expect(result.target_message_id).to eq(1)
end
end
end
context "when target_message_exists" do
context "when no target_message_id is given" do
it { is_expected.to run_successfully } it { is_expected.to run_successfully }
end end
end
end
context "when determine_target_message_id" do context "when target message is not found" do
let(:optional_params) { { fetch_from_last_message: true } } let(:optional_params) { { target_message_id: -1 } }
context "when fetch_from_last_message is true" do
it "sets target_message_id to last thread message id" do
expect(result.target_message_id).to eq(thread.chat_messages.last.id)
end
end
context "when fetch_from_first_message is true" do
it "sets target_message_id to first thread message id" do
expect(result.target_message_id).to eq(thread.chat_messages.first.id)
end
end
context "when fetch_from_last_read is true" do
let(:optional_params) { { fetch_from_last_read: true } }
before do
thread.add(user)
thread.membership_for(guardian.user).update!(last_read_message_id: 1)
end
it "sets target_message_id to last_read_message_id" do
expect(result.target_message_id).to eq(1)
end
end
end
context "when target_message_exists" do
context "when no target_message_id is given" do
it { is_expected.to run_successfully }
end
context "when target message is not found" do
let(:optional_params) { { target_message_id: -1 } }
it { is_expected.to fail_a_policy(:target_message_exists) }
end
context "when target message is found" do
fab!(:target_message) do
Fabricate(:chat_message, chat_channel: thread.channel, thread: thread)
end
let(:optional_params) { { target_message_id: target_message.id } }
it { is_expected.to run_successfully }
end
context "when target message is trashed" do
fab!(:target_message) do
Fabricate(:chat_message, chat_channel: thread.channel, thread: thread)
end
let(:optional_params) { { target_message_id: target_message.id } }
before { target_message.trash! }
context "when user is regular" do
it { is_expected.to fail_a_policy(:target_message_exists) } it { is_expected.to fail_a_policy(:target_message_exists) }
end end
context "when user is the message creator" do context "when target message is found" do
fab!(:target_message) do fab!(:target_message) do
Fabricate(:chat_message, chat_channel: thread.channel, thread: thread, user: user) Fabricate(:chat_message, chat_channel: thread.channel, thread: thread)
end
let(:optional_params) { { target_message_id: target_message.id } }
it { is_expected.to run_successfully }
end
context "when target message is trashed" do
fab!(:target_message) do
Fabricate(:chat_message, chat_channel: thread.channel, thread: thread)
end
let(:optional_params) { { target_message_id: target_message.id } }
before { target_message.trash! }
context "when user is regular" do
it { is_expected.to fail_a_policy(:target_message_exists) }
end end
it { is_expected.to run_successfully } context "when user is the message creator" do
end fab!(:target_message) do
Fabricate(:chat_message, chat_channel: thread.channel, thread: thread, user: user)
end
context "when user is admin" do it { is_expected.to run_successfully }
fab!(:user) { Fabricate(:admin) } end
it { is_expected.to run_successfully } context "when user is admin" do
end fab!(:user) { Fabricate(:admin) }
end
end
context "when fetch_messages" do it { is_expected.to run_successfully }
context "with not params" do end
fab!(:messages) do
Fabricate.times(20, :chat_message, chat_channel: thread.channel, thread: thread)
end
it "returns messages" do
expect(result.can_load_more_past).to eq(false)
expect(result.can_load_more_future).to eq(false)
expect(result.messages).to contain_exactly(thread.original_message, *messages)
end end
end end
context "when target_date is provided" do context "when fetch_messages" do
fab!(:past_message) do context "with not params" do
Fabricate( fab!(:messages) do
:chat_message, Fabricate.times(20, :chat_message, chat_channel: thread.channel, thread: thread)
chat_channel: thread.channel, end
thread: thread,
created_at: 1.days.from_now, it "returns messages" do
) expect(result.can_load_more_past).to eq(false)
end expect(result.can_load_more_future).to eq(false)
fab!(:future_message) do expect(result.messages).to contain_exactly(thread.original_message, *messages)
Fabricate( end
:chat_message,
chat_channel: thread.channel,
thread: thread,
created_at: 3.days.from_now,
)
end end
let(:optional_params) { { target_date: 2.days.ago } } context "when target_date is provided" do
fab!(:past_message) do
Fabricate(
:chat_message,
chat_channel: thread.channel,
thread: thread,
created_at: 1.days.from_now,
)
end
fab!(:future_message) do
Fabricate(
:chat_message,
chat_channel: thread.channel,
thread: thread,
created_at: 3.days.from_now,
)
end
it "includes past and future messages" do let(:optional_params) { { target_date: 2.days.ago } }
expect(result.messages).to eq([thread.original_message, past_message, future_message])
it "includes past and future messages" do
expect(result.messages).to eq([thread.original_message, past_message, future_message])
end
end end
end end
end end

View File

@ -1,13 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::ListUserChannels do RSpec.describe Chat::ListUserChannels do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel) } fab!(:channel_1) { Fabricate(:chat_channel) }
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:params) { { guardian: guardian } } let(:params) { {} }
let(:dependencies) { { guardian: } }
before { channel_1.add(current_user) } before { channel_1.add(current_user) }

View File

@ -11,7 +11,7 @@ RSpec.describe ::Chat::LookupChannelThreads::Contract, type: :model do
end end
RSpec.describe ::Chat::LookupChannelThreads do RSpec.describe ::Chat::LookupChannelThreads do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
@ -19,7 +19,8 @@ RSpec.describe ::Chat::LookupChannelThreads do
let(:channel_id) { nil } let(:channel_id) { nil }
let(:limit) { 10 } let(:limit) { 10 }
let(:offset) { 0 } let(:offset) { 0 }
let(:params) { { guardian: guardian, channel_id: channel_id, limit: limit, offset: offset } } let(:params) { { channel_id:, limit:, offset: } }
let(:dependencies) { { guardian: } }
describe "step - set_limit" do describe "step - set_limit" do
fab!(:channel_1) { Fabricate(:chat_channel) } fab!(:channel_1) { Fabricate(:chat_channel) }

View File

@ -1,13 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::LookupThread do RSpec.describe Chat::LookupThread do
describe Chat::LookupThread::Contract, type: :model do describe described_class::Contract, type: :model do
it { is_expected.to validate_presence_of :channel_id } it { is_expected.to validate_presence_of :channel_id }
it { is_expected.to validate_presence_of :thread_id } it { is_expected.to validate_presence_of :thread_id }
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:channel) { Fabricate(:chat_channel, threading_enabled: true) } fab!(:channel) { Fabricate(:chat_channel, threading_enabled: true) }
@ -16,7 +16,8 @@ RSpec.describe Chat::LookupThread do
fab!(:other_thread) { Fabricate(:chat_thread) } fab!(:other_thread) { Fabricate(:chat_thread) }
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:params) { { guardian: guardian, thread_id: thread.id, channel_id: thread.channel_id } } let(:params) { { thread_id: thread.id, channel_id: thread.channel_id } }
let(:dependencies) { { guardian: } }
context "when all steps pass" do context "when all steps pass" do
it { is_expected.to run_successfully } it { is_expected.to run_successfully }

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe ::Chat::LookupUserThreads do RSpec.describe ::Chat::LookupUserThreads do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel, threading_enabled: true) } fab!(:channel_1) { Fabricate(:chat_channel, threading_enabled: true) }
@ -11,7 +11,8 @@ RSpec.describe ::Chat::LookupUserThreads do
let(:channel_id) { channel_1.id } let(:channel_id) { channel_1.id }
let(:limit) { 10 } let(:limit) { 10 }
let(:offset) { 0 } let(:offset) { 0 }
let(:params) { { guardian: guardian, limit: limit, offset: offset } } let(:params) { { limit:, offset: } }
let(:dependencies) { { guardian: } }
before do before do
channel_1.add(current_user) channel_1.add(current_user)

View File

@ -2,9 +2,10 @@
RSpec.describe Chat::MarkAllUserChannelsRead do RSpec.describe Chat::MarkAllUserChannelsRead do
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
let(:params) { { guardian: guardian } } let(:params) { {} }
let(:dependencies) { { guardian: } }
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }

View File

@ -1,13 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::MarkThreadTitlePromptSeen do RSpec.describe Chat::MarkThreadTitlePromptSeen do
describe Chat::MarkThreadTitlePromptSeen::Contract, type: :model do describe described_class::Contract, type: :model do
it { is_expected.to validate_presence_of :channel_id } it { is_expected.to validate_presence_of :channel_id }
it { is_expected.to validate_presence_of :thread_id } it { is_expected.to validate_presence_of :thread_id }
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:channel) { Fabricate(:chat_channel, threading_enabled: true) } fab!(:channel) { Fabricate(:chat_channel, threading_enabled: true) }
@ -18,7 +18,8 @@ RSpec.describe Chat::MarkThreadTitlePromptSeen do
fab!(:last_reply) { Fabricate(:chat_message, thread: thread, chat_channel: channel) } fab!(:last_reply) { Fabricate(:chat_message, thread: thread, chat_channel: channel) }
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:params) { { guardian: guardian, thread_id: thread.id, channel_id: thread.channel_id } } let(:params) { { thread_id: thread.id, channel_id: thread.channel_id } }
let(:dependencies) { { guardian: } }
before { thread.update!(last_message: last_reply) } before { thread.update!(last_message: last_reply) }

View File

@ -13,18 +13,18 @@ RSpec.describe Chat::RestoreMessage do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
let(:dependencies) { { guardian: } }
context "when params are not valid" do context "when params are not valid" do
let(:params) { { guardian: guardian } } let(:params) { {} }
it { is_expected.to fail_a_contract } it { is_expected.to fail_a_contract }
end end
context "when params are valid" do context "when params are valid" do
let(:params) do let(:params) { { message_id: message.id, channel_id: message.chat_channel_id } }
{ guardian: guardian, message_id: message.id, channel_id: message.chat_channel_id }
end
context "when the user does not have permission to restore" do context "when the user does not have permission to restore" do
before { message.update!(user: Fabricate(:admin)) } before { message.update!(user: Fabricate(:admin)) }
@ -33,9 +33,7 @@ RSpec.describe Chat::RestoreMessage do
end end
context "when the channel does not match the message" do context "when the channel does not match the message" do
let(:params) do let(:params) { { message_id: message.id, channel_id: Fabricate(:chat_channel).id } }
{ guardian: guardian, message_id: message.id, channel_id: Fabricate(:chat_channel).id }
end
it { is_expected.to fail_to_find_a_model(:message) } it { is_expected.to fail_to_find_a_model(:message) }
end end

View File

@ -2,7 +2,7 @@
RSpec.describe Chat::SearchChatable do RSpec.describe Chat::SearchChatable do
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user, username: "bob-user") } fab!(:current_user) { Fabricate(:user, username: "bob-user") }
fab!(:sam) { Fabricate(:user, username: "sam-user") } fab!(:sam) { Fabricate(:user, username: "sam-user") }
@ -25,15 +25,15 @@ RSpec.describe Chat::SearchChatable do
let(:excluded_memberships_channel_id) { nil } let(:excluded_memberships_channel_id) { nil }
let(:params) do let(:params) do
{ {
guardian: guardian, term:,
term: term, include_users:,
include_users: include_users, include_groups:,
include_groups: include_groups, include_category_channels:,
include_category_channels: include_category_channels, include_direct_message_channels:,
include_direct_message_channels: include_direct_message_channels, excluded_memberships_channel_id:,
excluded_memberships_channel_id: excluded_memberships_channel_id,
} }
end end
let(:dependencies) { { guardian: } }
before do before do
SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:everyone] SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:everyone]

View File

@ -6,10 +6,11 @@ RSpec.describe Chat::StopMessageStreaming do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:params) { { guardian: guardian, message_id: message_1.id } } let(:params) { { message_id: message_1.id } }
let(:dependencies) { { guardian: guardian } }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel) } fab!(:channel_1) { Fabricate(:chat_channel) }

View File

@ -2,7 +2,7 @@
RSpec.describe ::Chat::TrackingState do RSpec.describe ::Chat::TrackingState do
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel, threading_enabled: true) } fab!(:channel_1) { Fabricate(:chat_channel, threading_enabled: true) }
@ -17,12 +17,8 @@ RSpec.describe ::Chat::TrackingState do
let(:include_threads) { true } let(:include_threads) { true }
let(:include_missing_memberships) { nil } let(:include_missing_memberships) { nil }
let(:params) do let(:params) { id_params.merge(include_threads:, include_missing_memberships:) }
id_params.merge(guardian: guardian).merge( let(:dependencies) { { guardian: } }
include_threads: include_threads,
include_missing_memberships: include_missing_memberships,
)
end
fab!(:channel_1_membership) do fab!(:channel_1_membership) do
Fabricate(:user_chat_channel_membership, chat_channel: channel_1, user: current_user) Fabricate(:user_chat_channel_membership, chat_channel: channel_1, user: current_user)

View File

@ -1,30 +1,30 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe(Chat::TrashChannel) do RSpec.describe Chat::TrashChannel do
subject(:result) { described_class.call(guardian: guardian) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:admin) }
fab!(:channel) { Fabricate(:chat_channel) }
let(:params) { { channel_id: } }
let(:dependencies) { { guardian: } }
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:channel_id) { channel.id }
context "when channel_id is not provided" do context "when channel_id is not provided" do
fab!(:current_user) { Fabricate(:admin) } let(:channel_id) { nil }
it { is_expected.to fail_to_find_a_model(:channel) } it { is_expected.to fail_to_find_a_model(:channel) }
end end
context "when channel_id is provided" do context "when channel_id is provided" do
subject(:result) { described_class.call(channel_id: channel.id, guardian: guardian) }
fab!(:channel) { Fabricate(:chat_channel) }
context "when user is not allowed to perform the action" do context "when user is not allowed to perform the action" do
fab!(:current_user) { Fabricate(:user) } let!(:current_user) { Fabricate(:user) }
it { is_expected.to fail_a_policy(:invalid_access) } it { is_expected.to fail_a_policy(:invalid_access) }
end end
context "when user is allowed to perform the action" do context "when user is allowed to perform the action" do
fab!(:current_user) { Fabricate(:admin) }
it { is_expected.to run_successfully } it { is_expected.to run_successfully }
it "trashes the channel" do it "trashes the channel" do

View File

@ -7,7 +7,7 @@ RSpec.describe Chat::TrashMessage do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(**params, **dependencies) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:message) { Fabricate(:chat_message, user: current_user) } fab!(:message) { Fabricate(:chat_message, user: current_user) }

View File

@ -8,7 +8,7 @@ RSpec.describe Chat::TrashMessages do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(**params, **dependencies) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:chat_channel) { Fabricate(:chat_channel) } fab!(:chat_channel) { Fabricate(:chat_channel) }

View File

@ -8,27 +8,27 @@ RSpec.describe Chat::UnfollowChannel do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:channel_1) { Fabricate(:chat_channel) } fab!(:channel_1) { Fabricate(:chat_channel) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
let(:params) { { channel_id: } }
let(:dependencies) { { guardian: } }
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:channel_id) { channel_1.id } let(:channel_id) { channel_1.id }
before { SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:everyone] } before { SiteSetting.direct_message_enabled_groups = Group::AUTO_GROUPS[:everyone] }
let(:params) { { guardian: guardian, channel_id: channel_id } }
context "when all steps pass" do context "when all steps pass" do
context "with existing membership" do context "with existing membership" do
let(:membership) { channel_1.membership_for(current_user) }
before { channel_1.add(current_user) } before { channel_1.add(current_user) }
it { is_expected.to run_successfully } it { is_expected.to run_successfully }
it "unfollows the channel" do it "unfollows the channel" do
membership = channel_1.membership_for(current_user)
expect { result }.to change { membership.reload.following }.from(true).to(false) expect { result }.to change { membership.reload.following }.from(true).to(false)
end end
end end
@ -43,7 +43,7 @@ RSpec.describe Chat::UnfollowChannel do
end end
context "when channel is not found" do context "when channel is not found" do
before { params[:channel_id] = -999 } let(:channel_id) { -999 }
it { is_expected.to fail_to_find_a_model(:channel) } it { is_expected.to fail_to_find_a_model(:channel) }
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::UpdateChannel do RSpec.describe Chat::UpdateChannel do
subject(:result) { described_class.call(guardian: guardian, channel_id: channel.id, **params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:channel) { Fabricate(:chat_channel) } fab!(:channel) { Fabricate(:chat_channel) }
fab!(:current_user) { Fabricate(:admin) } fab!(:current_user) { Fabricate(:admin) }
@ -9,6 +9,7 @@ RSpec.describe Chat::UpdateChannel do
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:params) do let(:params) do
{ {
channel_id: channel.id,
name: "cool channel", name: "cool channel",
description: "a channel description", description: "a channel description",
slug: "snail", slug: "snail",
@ -16,6 +17,7 @@ RSpec.describe Chat::UpdateChannel do
auto_join_users: false, auto_join_users: false,
} }
end end
let(:dependencies) { { guardian: } }
context "when the user cannot edit the channel" do context "when the user cannot edit the channel" do
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }

View File

@ -8,7 +8,7 @@ RSpec.describe(Chat::UpdateChannelStatus) do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(**params, **dependencies) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:channel) { Fabricate(:chat_channel) } fab!(:channel) { Fabricate(:chat_channel) }
fab!(:current_user) { Fabricate(:admin) } fab!(:current_user) { Fabricate(:admin) }

View File

@ -64,7 +64,13 @@ RSpec.describe Chat::UpdateMessage do
new_message = "2 short" new_message = "2 short"
expect do expect do
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message) described_class.call(
guardian: guardian,
params: {
message_id: chat_message.id,
message: new_message,
},
)
end.to raise_error(ActiveRecord::RecordInvalid).with_message( end.to raise_error(ActiveRecord::RecordInvalid).with_message(
"Validation failed: " + "Validation failed: " +
I18n.t( I18n.t(
@ -83,7 +89,13 @@ RSpec.describe Chat::UpdateMessage do
new_message = "2 long" * 100 new_message = "2 long" * 100
expect do expect do
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message) described_class.call(
guardian: guardian,
params: {
message_id: chat_message.id,
message: new_message,
},
)
end.to raise_error(ActiveRecord::RecordInvalid).with_message( end.to raise_error(ActiveRecord::RecordInvalid).with_message(
"Validation failed: " + "Validation failed: " +
I18n.t( I18n.t(
@ -95,46 +107,17 @@ RSpec.describe Chat::UpdateMessage do
expect(chat_message.reload.message).to eq(og_message) expect(chat_message.reload.message).to eq(og_message)
end end
it "errors when a blank message is sent" do
og_message = "This won't be changed!"
chat_message = create_chat_message(user1, og_message, public_chat_channel)
new_message = " "
updater =
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message)
expect(updater.contract).not_to be_valid
expect(updater.contract.errors.added?(:message, :blank)).to be_truthy
expect(chat_message.reload.message).to eq(og_message)
end
it "errors if a user other than the message user is trying to edit the message" do
og_message = "This won't be changed!"
chat_message = create_chat_message(user1, og_message, public_chat_channel)
new_message = "2 short"
updater =
described_class.call(
guardian: Guardian.new(Fabricate(:user)),
message_id: chat_message.id,
message: new_message,
)
expect(updater.message.reload.message).not_to eq(new_message)
end
it "updates a message's content" do
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
new_message = "Change to this!"
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message)
expect(chat_message.reload.message).to eq(new_message)
end
it "cleans message's content" do it "cleans message's content" do
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel) chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
new_message = "bbbbb\n" new_message = "bbbbb\n"
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message) described_class.call(
guardian: guardian,
params: {
message_id: chat_message.id,
message: new_message,
},
)
expect(chat_message.reload.message).to eq("bbbbb") expect(chat_message.reload.message).to eq("bbbbb")
end end
@ -145,9 +128,13 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, options: {
message: new_message, strip_whitespaces: false,
strip_whitespaces: false, },
params: {
message_id: chat_message.id,
message: new_message,
},
) )
expect(chat_message.reload.message).to eq("bbbbb\n") expect(chat_message.reload.message).to eq("bbbbb\n")
end end
@ -157,7 +144,13 @@ RSpec.describe Chat::UpdateMessage do
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel) chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
new_message = "Change **to** this!" new_message = "Change **to** this!"
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message) described_class.call(
guardian: guardian,
params: {
message_id: chat_message.id,
message: new_message,
},
)
expect(chat_message.reload.cooked).to eq("<p>Change <strong>to</strong> this!</p>") expect(chat_message.reload.cooked).to eq("<p>Change <strong>to</strong> this!</p>")
end end
@ -166,8 +159,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "Change to this!", message_id: chat_message.id,
message: "Change to this!",
},
) )
expect(chat_message.reload.excerpt).to eq("Change to this!") expect(chat_message.reload.excerpt).to eq("Change to this!")
end end
@ -178,8 +173,10 @@ RSpec.describe Chat::UpdateMessage do
DiscourseEvent.track_events do DiscourseEvent.track_events do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "Change to this!", message_id: chat_message.id,
message: "Change to this!",
},
) )
end end
expect(events.map { _1[:event_name] }).to include(:chat_message_edited) expect(events.map { _1[:event_name] }).to include(:chat_message_edited)
@ -194,8 +191,10 @@ RSpec.describe Chat::UpdateMessage do
.track_publish("/chat/#{public_chat_channel.id}") do .track_publish("/chat/#{public_chat_channel.id}") do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: new_content, message_id: chat_message.id,
message: new_content,
},
) )
end end
.detect { |m| m.data["type"] == "edit" } .detect { |m| m.data["type"] == "edit" }
@ -210,8 +209,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: message.id, params: {
message: "Mentioning @#{user2.username} and @#{user3.username}", message_id: message.id,
message: "Mentioning @#{user2.username} and @#{user3.username}",
},
) )
mention = user3.chat_mentions.where(chat_message: message.id).first mention = user3.chat_mentions.where(chat_message: message.id).first
@ -224,8 +225,10 @@ RSpec.describe Chat::UpdateMessage do
expect { expect {
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: message + " editedddd", message_id: chat_message.id,
message: message + " editedddd",
},
) )
}.not_to change { Chat::Mention.count } }.not_to change { Chat::Mention.count }
end end
@ -236,8 +239,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: message + " @#{user_without_memberships.username}", message_id: chat_message.id,
message: message + " @#{user_without_memberships.username}",
},
) )
mention = user_without_memberships.chat_mentions.where(chat_message: chat_message).first mention = user_without_memberships.chat_mentions.where(chat_message: chat_message).first
@ -254,8 +259,10 @@ RSpec.describe Chat::UpdateMessage do
expect { expect {
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "ping @#{user3.username}", message_id: chat_message.id,
message: "ping @#{user3.username}",
},
) )
}.to change { user2.chat_mentions.count }.by(-1).and not_change { }.to change { user2.chat_mentions.count }.by(-1).and not_change {
user3.chat_mentions.count user3.chat_mentions.count
@ -271,8 +278,10 @@ RSpec.describe Chat::UpdateMessage do
) )
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "ping @#{user3.username} @#{user4.username}", message_id: chat_message.id,
message: "ping @#{user3.username} @#{user4.username}",
},
) )
expect(user2.chat_mentions.where(chat_message: chat_message)).not_to be_present expect(user2.chat_mentions.where(chat_message: chat_message)).not_to be_present
@ -284,7 +293,9 @@ RSpec.describe Chat::UpdateMessage do
result = result =
Chat::CreateDirectMessageChannel.call( Chat::CreateDirectMessageChannel.call(
guardian: user1.guardian, guardian: user1.guardian,
target_usernames: [user1.username, user2.username], params: {
target_usernames: [user1.username, user2.username],
},
) )
service_failed!(result) if result.failure? service_failed!(result) if result.failure?
direct_message_channel = result.channel direct_message_channel = result.channel
@ -292,8 +303,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: message.id, params: {
message: "ping @#{admin1.username}", message_id: message.id,
message: "ping @#{admin1.username}",
},
) )
mention = admin1.chat_mentions.where(chat_message_id: message.id).first mention = admin1.chat_mentions.where(chat_message_id: message.id).first
@ -304,7 +317,13 @@ RSpec.describe Chat::UpdateMessage do
chat_message = create_chat_message(user1, "I will mention myself soon", public_chat_channel) chat_message = create_chat_message(user1, "I will mention myself soon", public_chat_channel)
new_content = "hello @#{user1.username}" new_content = "hello @#{user1.username}"
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_content) described_class.call(
guardian: guardian,
params: {
message_id: chat_message.id,
message: new_content,
},
)
mention = user1.chat_mentions.where(chat_message: chat_message).first mention = user1.chat_mentions.where(chat_message: chat_message).first
expect(mention).to be_present expect(mention).to be_present
@ -323,8 +342,10 @@ RSpec.describe Chat::UpdateMessage do
.track_publish("/chat/#{public_chat_channel.id}") do .track_publish("/chat/#{public_chat_channel.id}") do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: new_content, message_id: chat_message.id,
message: new_content,
},
) )
end end
.detect { |m| m.data["type"] == "processed" } .detect { |m| m.data["type"] == "processed" }
@ -349,8 +370,10 @@ RSpec.describe Chat::UpdateMessage do
.track_publish("/chat/#{public_chat_channel.id}") do .track_publish("/chat/#{public_chat_channel.id}") do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "Hey @#{user2.username}", message_id: chat_message.id,
message: "Hey @#{user2.username}",
},
) )
end end
.detect { |m| m.data["type"] == "processed" } .detect { |m| m.data["type"] == "processed" }
@ -368,8 +391,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "ping @#{user3.username}", message_id: chat_message.id,
message: "ping @#{user3.username}",
},
) )
user2_mentions = user2.chat_mentions.where(chat_message: chat_message) user2_mentions = user2.chat_mentions.where(chat_message: chat_message)
@ -386,8 +411,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "ping @#{user2.username} @#{user2.username} edited", message_id: chat_message.id,
message: "ping @#{user2.username} @#{user2.username} edited",
},
) )
expect(user2.chat_mentions.where(chat_message: chat_message).count).to eq(1) expect(user2.chat_mentions.where(chat_message: chat_message).count).to eq(1)
@ -415,8 +442,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "ping @#{group_1.name}", message_id: chat_message.id,
message: "ping @#{group_1.name}",
},
) )
expect(group_1.chat_mentions.where(chat_message: chat_message).count).to be(1) expect(group_1.chat_mentions.where(chat_message: chat_message).count).to be(1)
@ -429,8 +458,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "ping @#{group_2.name}", message_id: chat_message.id,
message: "ping @#{group_2.name}",
},
) )
expect(chat_message.reload.group_mentions.map(&:target_id)).to contain_exactly(group_2.id) expect(chat_message.reload.group_mentions.map(&:target_id)).to contain_exactly(group_2.id)
@ -441,8 +472,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "ping nobody anymore!", message_id: chat_message.id,
message: "ping nobody anymore!",
},
) )
expect(group_1.chat_mentions.where(chat_message: chat_message).count).to be(0) expect(group_1.chat_mentions.where(chat_message: chat_message).count).to be(0)
@ -465,8 +498,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "Update the message and still mention the same group @#{group.name}", message_id: chat_message.id,
message: "Update the message and still mention the same group @#{group.name}",
},
) )
expect(group_user.notifications.count).to be(1) # no new notifications has been created expect(group_user.notifications.count).to be(1) # no new notifications has been created
@ -486,8 +521,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "Update the message and still mention @here", message_id: chat_message.id,
message: "Update the message and still mention @here",
},
) )
expect(user.notifications.count).to be(1) # no new notifications have been created expect(user.notifications.count).to be(1) # no new notifications have been created
@ -505,8 +542,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "Update the message and still mention @all", message_id: chat_message.id,
message: "Update the message and still mention @all",
},
) )
expect(user.notifications.count).to be(1) # no new notifications have been created expect(user.notifications.count).to be(1) # no new notifications have been created
@ -522,7 +561,13 @@ RSpec.describe Chat::UpdateMessage do
old_message = "It's a thrsday!" old_message = "It's a thrsday!"
new_message = "Today is Thursday, it's almost the weekend already!" new_message = "Today is Thursday, it's almost the weekend already!"
chat_message = create_chat_message(user1, old_message, public_chat_channel) chat_message = create_chat_message(user1, old_message, public_chat_channel)
described_class.call(guardian: guardian, message_id: chat_message.id, message: new_message) described_class.call(
guardian: guardian,
params: {
message_id: chat_message.id,
message: new_message,
},
)
revision = chat_message.revisions.last revision = chat_message.revisions.last
expect(revision.old_message).to eq(old_message) expect(revision.old_message).to eq(old_message)
@ -556,8 +601,10 @@ RSpec.describe Chat::UpdateMessage do
expect do expect do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message_1.id, params: {
message: "another different chat message here", message_id: chat_message_1.id,
message: "another different chat message here",
},
) )
end.to raise_error(ActiveRecord::RecordInvalid).with_message( end.to raise_error(ActiveRecord::RecordInvalid).with_message(
"Validation failed: " + I18n.t("chat.errors.duplicate_message"), "Validation failed: " + I18n.t("chat.errors.duplicate_message"),
@ -577,9 +624,11 @@ RSpec.describe Chat::UpdateMessage do
updater = updater =
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "this is some chat message", message_id: chat_message.id,
upload_ids: [upload2.id], message: "this is some chat message",
upload_ids: [upload2.id],
},
) )
expect(updater.message).to be_valid expect(updater.message).to be_valid
expect(chat_message.reload.uploads.count).to eq(1) expect(chat_message.reload.uploads.count).to eq(1)
@ -601,9 +650,11 @@ RSpec.describe Chat::UpdateMessage do
expect { expect {
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "I guess this is different", message_id: chat_message.id,
upload_ids: [upload2.id, upload1.id], message: "I guess this is different",
upload_ids: [upload2.id, upload1.id],
},
) )
}.to not_change { UploadReference.count } }.to not_change { UploadReference.count }
end end
@ -620,9 +671,11 @@ RSpec.describe Chat::UpdateMessage do
expect { expect {
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "I guess this is different", message_id: chat_message.id,
upload_ids: [upload1.id], message: "I guess this is different",
upload_ids: [upload1.id],
},
) )
}.to change { UploadReference.where(upload_id: upload2.id).count }.by(-1) }.to change { UploadReference.where(upload_id: upload2.id).count }.by(-1)
end end
@ -639,9 +692,11 @@ RSpec.describe Chat::UpdateMessage do
expect { expect {
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "I guess this is different", message_id: chat_message.id,
upload_ids: [], message: "I guess this is different",
upload_ids: [],
},
) )
}.to change { UploadReference.where(target: chat_message).count }.by(-2) }.to change { UploadReference.where(target: chat_message).count }.by(-2)
end end
@ -651,9 +706,11 @@ RSpec.describe Chat::UpdateMessage do
expect { expect {
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "I guess this is different", message_id: chat_message.id,
upload_ids: [upload1.id], message: "I guess this is different",
upload_ids: [upload1.id],
},
) )
}.to change { UploadReference.where(target: chat_message).count }.by(1) }.to change { UploadReference.where(target: chat_message).count }.by(1)
end end
@ -663,9 +720,11 @@ RSpec.describe Chat::UpdateMessage do
expect { expect {
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "I guess this is different", message_id: chat_message.id,
upload_ids: [upload1.id, upload2.id], message: "I guess this is different",
upload_ids: [upload1.id, upload2.id],
},
) )
}.to change { UploadReference.where(target: chat_message).count }.by(2) }.to change { UploadReference.where(target: chat_message).count }.by(2)
end end
@ -676,9 +735,11 @@ RSpec.describe Chat::UpdateMessage do
expect { expect {
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message, params: {
message: "I guess this is different", message_id: chat_message,
upload_ids: [0], message: "I guess this is different",
upload_ids: [0],
},
) )
}.to not_change { UploadReference.where(target: chat_message).count } }.to not_change { UploadReference.where(target: chat_message).count }
end end
@ -689,9 +750,11 @@ RSpec.describe Chat::UpdateMessage do
expect { expect {
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "I guess this is different", message_id: chat_message.id,
upload_ids: [upload1.id, upload2.id], message: "I guess this is different",
upload_ids: [upload1.id, upload2.id],
},
) )
}.to not_change { UploadReference.where(target: chat_message).count } }.to not_change { UploadReference.where(target: chat_message).count }
end end
@ -708,9 +771,11 @@ RSpec.describe Chat::UpdateMessage do
expect { expect {
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "I guess this is different", message_id: chat_message.id,
upload_ids: [], message: "I guess this is different",
upload_ids: [],
},
) )
}.to not_change { UploadReference.where(target: chat_message).count } }.to not_change { UploadReference.where(target: chat_message).count }
end end
@ -727,9 +792,11 @@ RSpec.describe Chat::UpdateMessage do
new_message = "hi :)" new_message = "hi :)"
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: new_message, message_id: chat_message.id,
upload_ids: [upload1.id], message: new_message,
upload_ids: [upload1.id],
},
) )
expect(chat_message.reload.message).to eq(new_message) expect(chat_message.reload.message).to eq(new_message)
end end
@ -750,8 +817,10 @@ RSpec.describe Chat::UpdateMessage do
MessageBus.track_publish("/chat/#{public_chat_channel.id}") do MessageBus.track_publish("/chat/#{public_chat_channel.id}") do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: message.id, params: {
message: "some new updated content", message_id: message.id,
message: "some new updated content",
},
) )
end end
expect( expect(
@ -773,8 +842,10 @@ RSpec.describe Chat::UpdateMessage do
expect do expect do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "bad word - #{watched_word.word}", message_id: chat_message.id,
message: "bad word - #{watched_word.word}",
},
) )
end.to raise_error(ActiveRecord::RecordInvalid).with_message(msg) end.to raise_error(ActiveRecord::RecordInvalid).with_message(msg)
@ -786,8 +857,10 @@ RSpec.describe Chat::UpdateMessage do
described_class.call( described_class.call(
guardian: guardian, guardian: guardian,
message_id: chat_message.id, params: {
message: "bad word - #{censored_word.word}", message_id: chat_message.id,
message: "bad word - #{censored_word.word}",
},
) )
expect(chat_message.reload.excerpt).to eq("bad word - ■■■■") expect(chat_message.reload.excerpt).to eq("bad word - ■■■■")
@ -801,8 +874,10 @@ RSpec.describe Chat::UpdateMessage do
message.update!(user: user) message.update!(user: user)
described_class.call( described_class.call(
guardian: Guardian.new(user), guardian: Guardian.new(user),
message_id: message.id, params: {
message: "I guess this is different", message_id: message.id,
message: "I guess this is different",
},
) )
end end
@ -810,7 +885,7 @@ RSpec.describe Chat::UpdateMessage do
before { public_chat_channel.update(status: :closed) } before { public_chat_channel.update(status: :closed) }
it "errors when trying to update the message for non-staff" do it "errors when trying to update the message for non-staff" do
updater = update_message(user1) update_message(user1)
expect(message.reload.message).not_to eq("I guess this is different") expect(message.reload.message).not_to eq("I guess this is different")
end end
@ -824,10 +899,10 @@ RSpec.describe Chat::UpdateMessage do
before { public_chat_channel.update(status: :read_only) } before { public_chat_channel.update(status: :read_only) }
it "errors when trying to update the message for all users" do it "errors when trying to update the message for all users" do
updater = update_message(user1) update_message(user1)
expect(message.reload.message).not_to eq("I guess this is different") expect(message.reload.message).not_to eq("I guess this is different")
updater = update_message(admin1) update_message(admin1)
expect(message.reload.message).not_to eq("I guess this is different") expect(message.reload.message).not_to eq("I guess this is different")
end end
end end
@ -836,10 +911,10 @@ RSpec.describe Chat::UpdateMessage do
before { public_chat_channel.update(status: :archived) } before { public_chat_channel.update(status: :archived) }
it "errors when trying to update the message for all users" do it "errors when trying to update the message for all users" do
updater = update_message(user1) update_message(user1)
expect(message.reload.message).not_to eq("I guess this is different") expect(message.reload.message).not_to eq("I guess this is different")
updater = update_message(admin1) update_message(admin1)
expect(message.reload.message).not_to eq("I guess this is different") expect(message.reload.message).not_to eq("I guess this is different")
end end
end end
@ -847,7 +922,7 @@ RSpec.describe Chat::UpdateMessage do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, options:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:channel_1) { Fabricate(:chat_channel) } fab!(:channel_1) { Fabricate(:chat_channel) }
@ -866,9 +941,9 @@ RSpec.describe Chat::UpdateMessage do
let(:message) { "new" } let(:message) { "new" }
let(:message_id) { message_1.id } let(:message_id) { message_1.id }
let(:upload_ids) { [upload_1.id] } let(:upload_ids) { [upload_1.id] }
let(:params) do let(:params) { { message_id: message_id, message: message, upload_ids: upload_ids } }
{ guardian: guardian, message_id: message_id, message: message, upload_ids: upload_ids } let(:dependencies) { { guardian: guardian } }
end let(:options) { {} }
before do before do
SiteSetting.chat_editing_grace_period = 30 SiteSetting.chat_editing_grace_period = 30
@ -905,12 +980,12 @@ RSpec.describe Chat::UpdateMessage do
end end
it "can enqueue a job to process message" do it "can enqueue a job to process message" do
params[:process_inline] = false options[:process_inline] = false
expect_enqueued_with(job: Jobs::Chat::ProcessMessage) { result } expect_enqueued_with(job: Jobs::Chat::ProcessMessage) { result }
end end
it "can process a message inline" do it "can process a message inline" do
params[:process_inline] = true options[:process_inline] = true
Jobs::Chat::ProcessMessage.any_instance.expects(:execute).once Jobs::Chat::ProcessMessage.any_instance.expects(:execute).once
expect_not_enqueued_with(job: Jobs::Chat::ProcessMessage) { result } expect_not_enqueued_with(job: Jobs::Chat::ProcessMessage) { result }
end end

View File

@ -11,7 +11,7 @@ RSpec.describe Chat::UpdateThreadNotificationSettings do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:channel) { Fabricate(:chat_channel, threading_enabled: true) } fab!(:channel) { Fabricate(:chat_channel, threading_enabled: true) }
@ -22,12 +22,12 @@ RSpec.describe Chat::UpdateThreadNotificationSettings do
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:params) do let(:params) do
{ {
guardian: guardian,
thread_id: thread.id, thread_id: thread.id,
channel_id: thread.channel_id, channel_id: thread.channel_id,
notification_level: Chat::UserChatThreadMembership.notification_levels[:normal], notification_level: Chat::UserChatThreadMembership.notification_levels[:normal],
} }
end end
let(:dependencies) { { guardian: } }
before { thread.update!(last_message: last_reply) } before { thread.update!(last_message: last_reply) }

View File

@ -7,7 +7,7 @@ RSpec.describe Chat::UpdateThread do
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:current_user) { Fabricate(:user) } fab!(:current_user) { Fabricate(:user) }
fab!(:channel) { Fabricate(:chat_channel, threading_enabled: true) } fab!(:channel) { Fabricate(:chat_channel, threading_enabled: true) }
@ -17,7 +17,8 @@ RSpec.describe Chat::UpdateThread do
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:title) { "some new title :D" } let(:title) { "some new title :D" }
let(:params) { { guardian: guardian, thread_id: thread.id, title: title } } let(:params) { { thread_id: thread.id, title: } }
let(:dependencies) { { guardian: } }
context "when all steps pass" do context "when all steps pass" do
it { is_expected.to run_successfully } it { is_expected.to run_successfully }

View File

@ -1,13 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::UpdateUserChannelLastRead do RSpec.describe Chat::UpdateUserChannelLastRead do
describe Chat::UpdateUserChannelLastRead::Contract, type: :model do describe described_class::Contract, type: :model do
it { is_expected.to validate_presence_of :channel_id } it { is_expected.to validate_presence_of :channel_id }
it { is_expected.to validate_presence_of :message_id } it { is_expected.to validate_presence_of :message_id }
end end
describe ".call" do describe ".call" do
subject(:result) { described_class.call(params) } subject(:result) { described_class.call(params:, **dependencies) }
fab!(:chatters) { Fabricate(:group) } fab!(:chatters) { Fabricate(:group) }
fab!(:current_user) { Fabricate(:user, group_ids: [chatters.id]) } fab!(:current_user) { Fabricate(:user, group_ids: [chatters.id]) }
@ -18,7 +18,8 @@ RSpec.describe Chat::UpdateUserChannelLastRead do
let(:message_1) { Fabricate(:chat_message, chat_channel: membership.chat_channel) } let(:message_1) { Fabricate(:chat_message, chat_channel: membership.chat_channel) }
let(:guardian) { Guardian.new(current_user) } let(:guardian) { Guardian.new(current_user) }
let(:params) { { guardian: guardian, channel_id: channel.id, message_id: message_1.id } } let(:params) { { channel_id: channel.id, message_id: message_1.id } }
let(:dependencies) { { guardian: } }
before { SiteSetting.chat_allowed_groups = chatters } before { SiteSetting.chat_allowed_groups = chatters }

Some files were not shown because too many files have changed in this diff Show More