DEV: Replace params by the contract object in services

This patch replaces the parameters provided to a service through
`params` by the contract object.

That way, it allows better consistency when accessing input params. For
example, if you have a service without a contract, to access a
parameter, you need to use `params[:my_parameter]`. But with a contract,
you do this through `contract.my_parameter`. Now, with this patch,
you’ll be able to access it through `params.my_parameter` or
`params[:my_parameter]`.

Some methods have been added to the contract object to better mimic a
Hash. That way, when accessing/using `params`, you don’t have to think
too much about it:
- `params.my_key` is also accessible through `params[:my_key]`.
- `params.my_key = value` can also be done through `params[:my_key] =
  value`.
- `#slice` and `#merge` are available.
- `#to_hash` has been implemented, so the contract object will be
  automatically cast as a hash by Ruby depending on the context. For
  example, with an AR model, you can do this: `user.update(**params)`.
This commit is contained in:
Loïc Guitaut 2024-10-23 17:57:48 +02:00 committed by Loïc Guitaut
parent c7db44cfe7
commit 584424594e
60 changed files with 410 additions and 405 deletions

View File

@ -40,9 +40,9 @@ 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(params: { setting_name: id, new_value: value }, guardian:) do SiteSetting::Update.call(params: { setting_name: id, new_value: value }, guardian:) do
on_success do |contract:| on_success do |params:|
if update_existing_users if update_existing_users
SiteSettingUpdateExistingUsers.call(id, contract.new_value, previous_value) SiteSettingUpdateExistingUsers.call(id, params.new_value, previous_value)
end end
render body: nil render body: nil
end end

View File

@ -121,10 +121,10 @@ class Admin::UsersController < Admin::StaffController
def suspend def suspend
User::Suspend.call(service_params) do User::Suspend.call(service_params) do
on_success do |contract:, user:, full_reason:| on_success do |params:, user:, full_reason:|
render_json_dump( render_json_dump(
suspension: { suspension: {
suspend_reason: contract.reason, suspend_reason: params.reason,
full_suspend_reason: full_reason, full_suspend_reason: full_reason,
suspended_till: user.suspended_till, suspended_till: user.suspended_till,
suspended_at: user.suspended_at, suspended_at: user.suspended_at,

View File

@ -4,7 +4,7 @@ class Experiments::Toggle
include Service::Base include Service::Base
policy :current_user_is_admin policy :current_user_is_admin
contract do params do
attribute :setting_name, :string attribute :setting_name, :string
validates :setting_name, presence: true validates :setting_name, presence: true
end end
@ -17,14 +17,14 @@ class Experiments::Toggle
guardian.is_admin? guardian.is_admin?
end end
def setting_is_available(contract:) def setting_is_available(params:)
SiteSetting.respond_to?(contract.setting_name) SiteSetting.respond_to?(params[:setting_name])
end end
def toggle(contract:, guardian:) def toggle(params:, guardian:)
SiteSetting.set_and_log( SiteSetting.set_and_log(
contract.setting_name, params[:setting_name],
!SiteSetting.public_send(contract.setting_name), !SiteSetting.public_send(params[:setting_name]),
guardian.user, guardian.user,
) )
end end

View File

@ -4,7 +4,7 @@ class Flags::CreateFlag
include Service::Base include Service::Base
policy :invalid_access policy :invalid_access
contract do params do
attribute :name, :string attribute :name, :string
attribute :description, :string attribute :description, :string
attribute :require_message, :boolean attribute :require_message, :boolean
@ -31,12 +31,12 @@ class Flags::CreateFlag
guardian.can_create_flag? guardian.can_create_flag?
end end
def unique_name(contract:) def unique_name(params:)
!Flag.custom.where(name: contract.name).exists? !Flag.custom.where(name: params[:name]).exists?
end end
def instantiate_flag(contract:) def instantiate_flag(params:)
Flag.new(contract.attributes.merge(notify_type: true)) Flag.new(params.merge(notify_type: true))
end end
def create(flag:) def create(flag:)

View File

@ -3,7 +3,7 @@
class Flags::ReorderFlag class Flags::ReorderFlag
include Service::Base include Service::Base
contract do params do
attribute :flag_id, :integer attribute :flag_id, :integer
attribute :direction, :string attribute :direction, :string
@ -21,8 +21,8 @@ class Flags::ReorderFlag
private private
def fetch_flag(contract:) def fetch_flag(params:)
Flag.find_by(id: contract.flag_id) Flag.find_by(id: params[:flag_id])
end end
def invalid_access(guardian:, flag:) def invalid_access(guardian:, flag:)
@ -33,25 +33,25 @@ class Flags::ReorderFlag
Flag.where.not(name_key: "notify_user").order(:position).to_a Flag.where.not(name_key: "notify_user").order(:position).to_a
end end
def invalid_move(flag:, contract:, all_flags:) def invalid_move(flag:, params:, all_flags:)
return false if all_flags.first == flag && contract.direction == "up" return false if all_flags.first == flag && params[:direction] == "up"
return false if all_flags.last == flag && contract.direction == "down" return false if all_flags.last == flag && params[:direction] == "down"
true true
end end
def move(flag:, contract:, all_flags:) def move(flag:, params:, all_flags:)
old_position = flag.position old_position = flag.position
index = all_flags.index(flag) index = all_flags.index(flag)
target_flag = all_flags[contract.direction == "up" ? index - 1 : index + 1] target_flag = all_flags[params[:direction] == "up" ? index - 1 : index + 1]
flag.update!(position: target_flag.position) flag.update!(position: target_flag.position)
target_flag.update!(position: old_position) target_flag.update!(position: old_position)
end end
def log(guardian:, flag:, contract:) def log(guardian:, flag:, params:)
StaffActionLogger.new(guardian.user).log_custom( StaffActionLogger.new(guardian.user).log_custom(
"move_flag", "move_flag",
{ flag: flag.name, direction: contract.direction }, { flag: flag.name, direction: params[:direction] },
) )
end end
end end

View File

@ -4,7 +4,7 @@ class Flags::ToggleFlag
include Service::Base include Service::Base
policy :invalid_access policy :invalid_access
contract do params do
attribute :flag_id, :integer attribute :flag_id, :integer
validates :flag_id, presence: true validates :flag_id, presence: true
@ -21,8 +21,8 @@ class Flags::ToggleFlag
guardian.can_toggle_flag? guardian.can_toggle_flag?
end end
def fetch_flag(contract:) def fetch_flag(params:)
Flag.find_by(id: contract.flag_id) Flag.find_by(id: params[:flag_id])
end end
def toggle(flag:) def toggle(flag:)

View File

@ -3,7 +3,7 @@
class Flags::UpdateFlag class Flags::UpdateFlag
include Service::Base include Service::Base
contract do params do
attribute :id, :integer attribute :id, :integer
attribute :name, :string attribute :name, :string
attribute :description, :string attribute :description, :string
@ -31,8 +31,8 @@ class Flags::UpdateFlag
private private
def fetch_flag(contract:) def fetch_flag(params:)
Flag.find_by(id: contract.id) Flag.find_by(id: params[:id])
end end
def not_system(flag:) def not_system(flag:)
@ -47,12 +47,12 @@ class Flags::UpdateFlag
guardian.can_edit_flag?(flag) guardian.can_edit_flag?(flag)
end end
def unique_name(contract:) def unique_name(params:)
!Flag.custom.where(name: contract.name).where.not(id: contract.id).exists? !Flag.custom.where(name: params[:name]).where.not(id: params[:id]).exists?
end end
def update(flag:, contract:) def update(flag:, params:)
flag.update!(contract.attributes) flag.update!(**params)
end end
def log(guardian:, flag:) def log(guardian:, flag:)

View File

@ -6,7 +6,7 @@ class SiteSetting::Update
options { attribute :allow_changing_hidden, :boolean, default: false } options { attribute :allow_changing_hidden, :boolean, default: false }
policy :current_user_is_admin policy :current_user_is_admin
contract do params do
attribute :setting_name attribute :setting_name
attribute :new_value attribute :new_value
@ -44,17 +44,17 @@ class SiteSetting::Update
guardian.is_admin? guardian.is_admin?
end end
def setting_is_visible(contract:, options:) def setting_is_visible(params:, options:)
options.allow_changing_hidden || !SiteSetting.hidden_settings.include?(contract.setting_name) options.allow_changing_hidden || !SiteSetting.hidden_settings.include?(params[:setting_name])
end end
def setting_is_configurable(contract:) def setting_is_configurable(params:)
return true if !SiteSetting.plugins[contract.setting_name] return true if !SiteSetting.plugins[params[:setting_name]]
Discourse.plugins_by_name[SiteSetting.plugins[contract.setting_name]].configurable? Discourse.plugins_by_name[SiteSetting.plugins[params[:setting_name]]].configurable?
end end
def save(contract:, guardian:) def save(params:, guardian:)
SiteSetting.set_and_log(contract.setting_name, contract.new_value, guardian.user) SiteSetting.set_and_log(params[:setting_name], params[:new_value], guardian.user)
end end
end end

View File

@ -3,9 +3,9 @@
class User::Action::SilenceAll < Service::ActionBase class User::Action::SilenceAll < Service::ActionBase
option :users, [] option :users, []
option :actor option :actor
option :contract option :params
delegate :message, :post_id, :silenced_till, :reason, to: :contract, private: true delegate :message, :post_id, :silenced_till, :reason, to: :params, private: true
def call def call
silenced_users.first.try(:user_history).try(:details) silenced_users.first.try(:user_history).try(:details)

View File

@ -3,9 +3,9 @@
class User::Action::SuspendAll < Service::ActionBase class User::Action::SuspendAll < Service::ActionBase
option :users, [] option :users, []
option :actor option :actor
option :contract option :params
delegate :message, :post_id, :suspend_until, :reason, to: :contract, private: true delegate :message, :post_id, :suspend_until, :reason, to: :params, private: true
def call def call
suspended_users.first.try(:user_history).try(:details) suspended_users.first.try(:user_history).try(:details)

View File

@ -3,9 +3,9 @@
class User::Action::TriggerPostAction < Service::ActionBase class User::Action::TriggerPostAction < Service::ActionBase
option :guardian option :guardian
option :post option :post
option :contract option :params
delegate :post_action, to: :contract, private: true delegate :post_action, to: :params, private: true
delegate :user, to: :guardian, private: true delegate :user, to: :guardian, private: true
def call def call
@ -30,7 +30,7 @@ class User::Action::TriggerPostAction < Service::ActionBase
# Take what the moderator edited in as gospel # Take what the moderator edited in as gospel
PostRevisor.new(post).revise!( PostRevisor.new(post).revise!(
user, user,
{ raw: contract.post_edit }, { raw: params.post_edit },
skip_validations: true, skip_validations: true,
skip_revision: true, skip_revision: true,
) )

View File

@ -3,7 +3,7 @@
class User::Silence class User::Silence
include Service::Base include Service::Base
contract do params do
attribute :user_id, :integer attribute :user_id, :integer
attribute :reason, :string attribute :reason, :string
attribute :message, :string attribute :message, :string
@ -29,27 +29,27 @@ class User::Silence
private private
def fetch_user(contract:) def fetch_user(params:)
User.find_by(id: contract.user_id) User.find_by(id: params[:user_id])
end end
def fetch_users(user:, contract:) def fetch_users(user:, params:)
[user, *User.where(id: contract.other_user_ids.to_a.uniq).to_a] [user, *User.where(id: params[:other_user_ids].to_a.uniq).to_a]
end end
def can_silence_all_users(guardian:, users:) def can_silence_all_users(guardian:, users:)
users.all? { guardian.can_silence_user?(_1) } users.all? { guardian.can_silence_user?(_1) }
end end
def silence(guardian:, users:, contract:) def silence(guardian:, users:, params:)
context[:full_reason] = User::Action::SilenceAll.call(users:, actor: guardian.user, contract:) context[:full_reason] = User::Action::SilenceAll.call(users:, actor: guardian.user, params:)
end end
def fetch_post(contract:) def fetch_post(params:)
Post.find_by(id: contract.post_id) Post.find_by(id: params[:post_id])
end end
def perform_post_action(guardian:, post:, contract:) def perform_post_action(guardian:, post:, params:)
User::Action::TriggerPostAction.call(guardian:, post:, contract:) User::Action::TriggerPostAction.call(guardian:, post:, params:)
end end
end end

View File

@ -3,7 +3,7 @@
class User::Suspend class User::Suspend
include Service::Base include Service::Base
contract do params do
attribute :user_id, :integer attribute :user_id, :integer
attribute :reason, :string attribute :reason, :string
attribute :message, :string attribute :message, :string
@ -29,27 +29,27 @@ class User::Suspend
private private
def fetch_user(contract:) def fetch_user(params:)
User.find_by(id: contract.user_id) User.find_by(id: params[:user_id])
end end
def fetch_users(user:, contract:) def fetch_users(user:, params:)
[user, *User.where(id: contract.other_user_ids.to_a.uniq).to_a] [user, *User.where(id: params[:other_user_ids].to_a.uniq).to_a]
end end
def can_suspend_all_users(guardian:, users:) def can_suspend_all_users(guardian:, users:)
users.all? { guardian.can_suspend?(_1) } users.all? { guardian.can_suspend?(_1) }
end end
def suspend(guardian:, users:, contract:) def suspend(guardian:, users:, params:)
context[:full_reason] = User::Action::SuspendAll.call(users:, actor: guardian.user, contract:) context[:full_reason] = User::Action::SuspendAll.call(users:, actor: guardian.user, params:)
end end
def fetch_post(contract:) def fetch_post(params:)
Post.find_by(id: contract.post_id) Post.find_by(id: params[:post_id])
end end
def perform_post_action(guardian:, post:, contract:) def perform_post_action(guardian:, post:, params:)
User::Action::TriggerPostAction.call(guardian:, post:, contract:) User::Action::TriggerPostAction.call(guardian:, post:, params:)
end end
end end

View File

@ -10,13 +10,11 @@ module Service
# #
# Currently, there are 5 types of steps: # Currently, there are 5 types of steps:
# #
# * +contract(name = :default)+: used to validate the input parameters, # * +params(name = :default)+: used to validate the input parameters,
# typically provided by a user calling an endpoint. A block has to be # typically provided by a user calling an endpoint. A block has to be
# defined to hold the validations. If the validations fail, the step will # defined to hold the validations. If the validations fail, the step will
# fail. Otherwise, the resulting contract will be available in # fail. Otherwise, the resulting contract will be available in
# +context[:contract]+. When calling +step(name)+ or +model(name = :model)+ # +context[:params]+.
# methods after validating a contract, the contract should be used as an
# argument instead of context attributes.
# * +model(name = :model)+: used to instantiate a model (either by building # * +model(name = :model)+: used to instantiate a model (either by building
# it or fetching it from the DB). If a falsy value is returned, then the # it or fetching it from the DB). If a falsy value is returned, then the
# step will fail. Otherwise the resulting object will be assigned in # step will fail. Otherwise the resulting object will be assigned in
@ -82,7 +80,7 @@ module Service
# include Service::Base # include Service::Base
# #
# model :channel # model :channel
# contract do # params do
# attribute :status # attribute :status
# validates :status, inclusion: { in: Chat::Channel.editable_statuses.keys } # validates :status, inclusion: { in: Chat::Channel.editable_statuses.keys }
# end # end

View File

@ -94,7 +94,7 @@ module Service
steps << ModelStep.new(name, step_name, optional: optional) steps << ModelStep.new(name, step_name, optional: optional)
end end
def contract(name = :default, default_values_from: nil, &block) def params(name = :default, default_values_from: nil, &block)
contract_class = Class.new(Service::ContractBase).tap { _1.class_eval(&block) } contract_class = Class.new(Service::ContractBase).tap { _1.class_eval(&block) }
const_set("#{name.to_s.classify.sub("Default", "")}Contract", contract_class) const_set("#{name.to_s.classify.sub("Default", "")}Contract", contract_class)
steps << ContractStep.new( steps << ContractStep.new(
@ -137,7 +137,7 @@ module Service
object = class_name&.new(context) object = class_name&.new(context)
method = object&.method(:call) || instance.method(method_name) method = object&.method(:call) || instance.method(method_name)
if method.parameters.any? { _1[0] != :keyreq } if method.parameters.any? { _1[0] != :keyreq }
raise "In #{type} '#{name}': default values in step implementations are not allowed. Maybe they could be defined in a contract?" raise "In #{type} '#{name}': default values in step implementations are not allowed. Maybe they could be defined in a params or options block?"
end end
args = context.slice(*method.parameters.select { _1[0] == :keyreq }.map(&:last)) args = context.slice(*method.parameters.select { _1[0] == :keyreq }.map(&:last))
context[result_key] = Context.build(object: object) context[result_key] = Context.build(object: object)
@ -214,7 +214,7 @@ module Service
private private
def contract_name def contract_name
return :contract if default? return :params if default?
:"#{name}_contract" :"#{name}_contract"
end end
@ -328,18 +328,18 @@ module Service
# end # end
# @!scope class # @!scope class
# @!method contract(name = :default, default_values_from: nil, &block) # @!method params(name = :default, default_values_from: nil, &block)
# @param name [Symbol] name for this contract # @param name [Symbol] name for this contract
# @param default_values_from [Symbol] name of the model to get default values from # @param default_values_from [Symbol] name of the model to get default values from
# @param block [Proc] a block containing validations # @param block [Proc] a block containing validations
# Checks the validity of the input parameters. # Checks the validity of the input parameters.
# Implements ActiveModel::Validations and ActiveModel::Attributes. # Implements ActiveModel::Validations and ActiveModel::Attributes.
# #
# It stores the resulting contract in +context[:contract]+ by default # It stores the resulting contract in +context[:params]+ by default
# (can be customized by providing the +name+ argument). # (can be customized by providing the +name+ argument).
# #
# @example # @example
# contract do # params do
# attribute :name # attribute :name
# validates :name, presence: true # validates :name, presence: true
# end # end

View File

@ -6,6 +6,20 @@ class Service::ContractBase
include ActiveModel::AttributeMethods include ActiveModel::AttributeMethods
include ActiveModel::Validations::Callbacks include ActiveModel::Validations::Callbacks
delegate :slice, :merge, to: :to_hash
def [](key)
public_send(key)
end
def []=(key, value)
public_send("#{key}=", value)
end
def to_hash
attributes.symbolize_keys
end
def raw_attributes def raw_attributes
@attributes.values_before_type_cast @attributes.values_before_type_cast
end end

View File

@ -27,6 +27,7 @@ class Service::StepsInspector
def type def type
self.class.name.split("::").last.downcase self.class.name.split("::").last.downcase
end end
alias inspect_type type
def emoji def emoji
"#{result_emoji}#{unexpected_result_emoji}" "#{result_emoji}#{unexpected_result_emoji}"
@ -37,7 +38,7 @@ class Service::StepsInspector
end end
def inspect def inspect
"#{" " * nesting_level}[#{type}] '#{name}' #{emoji}".rstrip "#{" " * nesting_level}[#{inspect_type}] '#{name}' #{emoji}".rstrip
end end
private private
@ -75,6 +76,10 @@ class Service::StepsInspector
def error def error
"#{step_result.errors.inspect}\n\nProvided parameters: #{step_result.parameters.pretty_inspect}" "#{step_result.errors.inspect}\n\nProvided parameters: #{step_result.parameters.pretty_inspect}"
end end
def inspect_type
"params"
end
end end
# @!visibility private # @!visibility private
@ -113,7 +118,7 @@ class Service::StepsInspector
# Inspect the provided result object. # Inspect the provided result object.
# Example output: # Example output:
# [1/4] [model] 'channel' ✅ # [1/4] [model] 'channel' ✅
# [2/4] [contract] 'default' ✅ # [2/4] [params] 'default' ✅
# [3/4] [policy] 'check_channel_permission' ❌ # [3/4] [policy] 'check_channel_permission' ❌
# [4/4] [step] 'change_status' # [4/4] [step] 'change_status'
# @return [String] the steps of the result object with their state # @return [String] the steps of the result object with their state

View File

@ -9,8 +9,8 @@ module Jobs
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(", "))
end end
on_model_not_found(:channel) do |contract:| on_model_not_found(:channel) do |params:|
Rails.logger.error("Channel not found (id=#{contract.channel_id})") Rails.logger.error("Channel not found (id=#{params.channel_id})")
end end
end end
end end

View File

@ -4,7 +4,7 @@ module Chat
module Action module Action
class CreateMembershipsForAutoJoin < Service::ActionBase class CreateMembershipsForAutoJoin < Service::ActionBase
option :channel option :channel
option :contract option :params
def call def call
::DB.query_single(<<~SQL, query_args) ::DB.query_single(<<~SQL, query_args)
@ -42,8 +42,8 @@ module Chat
def query_args def query_args
{ {
chat_channel_id: channel.id, chat_channel_id: channel.id,
start: contract.start_user_id, start: params.start_user_id,
end: contract.end_user_id, end: params.end_user_id,
suspended_until: Time.zone.now, suspended_until: Time.zone.now,
last_seen_at: 3.months.ago, last_seen_at: 3.months.ago,
channel_category: channel.category.id, channel_category: channel.category.id,

View File

@ -24,7 +24,7 @@ module Chat
# @option params [Array<String>] :usernames # @option params [Array<String>] :usernames
# @option params [Array<String>] :groups # @option params [Array<String>] :groups
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :usernames, :array attribute :usernames, :array
attribute :groups, :array attribute :groups, :array
attribute :channel_id, :integer attribute :channel_id, :integer
@ -49,8 +49,8 @@ module Chat
private private
def fetch_channel(contract:) def fetch_channel(params:)
::Chat::Channel.includes(:chatable).find_by(id: contract.channel_id) ::Chat::Channel.includes(:chatable).find_by(id: params[:channel_id])
end end
def can_add_users_to_channel(guardian:, channel:) def can_add_users_to_channel(guardian:, channel:)
@ -58,10 +58,10 @@ module Chat
channel.direct_message_channel? && channel.chatable.group channel.direct_message_channel? && channel.chatable.group
end end
def fetch_target_users(contract:, channel:) def fetch_target_users(params:, channel:)
::Chat::UsersFromUsernamesAndGroupsQuery.call( ::Chat::UsersFromUsernamesAndGroupsQuery.call(
usernames: contract.usernames, usernames: params[:usernames],
groups: contract.groups, groups: params[:groups],
excluded_user_ids: channel.chatable.direct_message_users.pluck(:user_id), excluded_user_ids: channel.chatable.direct_message_users.pluck(:user_id),
) )
end end

View File

@ -16,7 +16,7 @@ module Chat
class AutoJoinChannelBatch class AutoJoinChannelBatch
include Service::Base include Service::Base
contract do params do
# Backward-compatible attributes # Backward-compatible attributes
attribute :chat_channel_id, :integer attribute :chat_channel_id, :integer
attribute :starts_at, :integer attribute :starts_at, :integer
@ -44,14 +44,14 @@ module Chat
private private
def fetch_channel(contract:) def fetch_channel(params:)
::Chat::CategoryChannel.find_by(id: contract.channel_id, auto_join_users: true) ::Chat::CategoryChannel.find_by(id: params[:channel_id], auto_join_users: true)
end end
def create_memberships(channel:, contract:) def create_memberships(channel:, params:)
context[:added_user_ids] = ::Chat::Action::CreateMembershipsForAutoJoin.call( context[:added_user_ids] = ::Chat::Action::CreateMembershipsForAutoJoin.call(
channel: channel, channel:,
contract: contract, params:,
) )
end end

View File

@ -15,7 +15,7 @@ module Chat
include Service::Base include Service::Base
policy :chat_enabled policy :chat_enabled
contract do params do
attribute :category_id, :integer attribute :category_id, :integer
validates :category_id, presence: true validates :category_id, presence: true
@ -37,8 +37,8 @@ module Chat
SiteSetting.chat_enabled SiteSetting.chat_enabled
end end
def fetch_category(contract:) def fetch_category(params:)
Category.find_by(id: contract.category_id) Category.find_by(id: params[:category_id])
end end
def fetch_category_channel_ids(category:) def fetch_category_channel_ids(category:)

View File

@ -19,7 +19,7 @@ module Chat
include Service::Base include Service::Base
policy :chat_enabled policy :chat_enabled
contract { attribute :new_allowed_groups, :array } params { attribute :new_allowed_groups, :array }
policy :not_everyone_allowed policy :not_everyone_allowed
model :users model :users
model :memberships_to_remove model :memberships_to_remove
@ -32,11 +32,11 @@ module Chat
SiteSetting.chat_enabled SiteSetting.chat_enabled
end end
def not_everyone_allowed(contract:) def not_everyone_allowed(params:)
contract.new_allowed_groups.exclude?(Group::AUTO_GROUPS[:everyone]) params[:new_allowed_groups].exclude?(Group::AUTO_GROUPS[:everyone])
end end
def fetch_users(contract:) def fetch_users(params:)
User User
.real .real
.activated .activated
@ -46,8 +46,8 @@ module Chat
.joins(:user_chat_channel_memberships) .joins(:user_chat_channel_memberships)
.distinct .distinct
.then do |users| .then do |users|
break users if contract.new_allowed_groups.blank? break users if params[:new_allowed_groups].blank?
users.where(<<~SQL, contract.new_allowed_groups) users.where(<<~SQL, params[:new_allowed_groups])
users.id NOT IN ( users.id NOT IN (
SELECT DISTINCT group_users.user_id SELECT DISTINCT group_users.user_id
FROM group_users FROM group_users

View File

@ -22,7 +22,7 @@ module Chat
include Service::Base include Service::Base
policy :chat_enabled policy :chat_enabled
contract do params do
attribute :destroyed_group_user_ids, :array attribute :destroyed_group_user_ids, :array
validates :destroyed_group_user_ids, presence: true validates :destroyed_group_user_ids, presence: true
@ -48,7 +48,7 @@ module Chat
!SiteSetting.chat_allowed_groups_map.include?(Group::AUTO_GROUPS[:everyone]) !SiteSetting.chat_allowed_groups_map.include?(Group::AUTO_GROUPS[:everyone])
end end
def fetch_scoped_users(contract:) def fetch_scoped_users(params:)
User User
.real .real
.activated .activated
@ -56,7 +56,7 @@ module Chat
.not_staged .not_staged
.includes(:group_users) .includes(:group_users)
.where("NOT admin AND NOT moderator") .where("NOT admin AND NOT moderator")
.where(id: contract.destroyed_group_user_ids) .where(id: params[:destroyed_group_user_ids])
.joins(:user_chat_channel_memberships) .joins(:user_chat_channel_memberships)
.distinct .distinct
end end

View File

@ -21,7 +21,7 @@ module Chat
include Service::Base include Service::Base
policy :chat_enabled policy :chat_enabled
contract do params do
attribute :user_id, :integer attribute :user_id, :integer
validates :user_id, presence: true validates :user_id, presence: true
@ -48,8 +48,8 @@ module Chat
!SiteSetting.chat_allowed_groups_map.include?(Group::AUTO_GROUPS[:everyone]) !SiteSetting.chat_allowed_groups_map.include?(Group::AUTO_GROUPS[:everyone])
end end
def fetch_user(contract:) def fetch_user(params:)
User.find_by(id: contract.user_id) User.find_by(id: params[:user_id])
end end
def user_not_staff(user:) def user_not_staff(user:)

View File

@ -31,7 +31,7 @@ module Chat
policy :public_channels_enabled policy :public_channels_enabled
policy :can_create_channel policy :can_create_channel
contract do params do
attribute :name, :string attribute :name, :string
attribute :description, :string attribute :description, :string
attribute :slug, :string attribute :slug, :string
@ -65,22 +65,18 @@ module Chat
guardian.can_create_chat_channel? guardian.can_create_chat_channel?
end end
def fetch_category(contract:) def fetch_category(params:)
Category.find_by(id: contract.category_id) Category.find_by(id: params[:category_id])
end end
def category_channel_does_not_exist(category:, contract:) def category_channel_does_not_exist(category:, params:)
!Chat::Channel.exists?(chatable: category, name: contract.name) !Chat::Channel.exists?(chatable: category, name: params[:name])
end end
def create_channel(category:, contract:) def create_channel(category:, params:)
category.create_chat_channel( category.create_chat_channel(
name: contract.name,
slug: contract.slug,
description: contract.description,
user_count: 1, user_count: 1,
auto_join_users: contract.auto_join_users, **params.slice(:name, :slug, :description, :auto_join_users, :threading_enabled),
threading_enabled: contract.threading_enabled,
) )
end end

View File

@ -25,7 +25,7 @@ module Chat
# @option params [Boolean] :upsert # @option params [Boolean] :upsert
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :name, :string attribute :name, :string
attribute :target_usernames, :array attribute :target_usernames, :array
attribute :target_groups, :array attribute :target_groups, :array
@ -62,10 +62,10 @@ module Chat
) )
end end
def fetch_target_users(guardian:, contract:) def fetch_target_users(guardian:, params:)
::Chat::UsersFromUsernamesAndGroupsQuery.call( ::Chat::UsersFromUsernamesAndGroupsQuery.call(
usernames: [*contract.target_usernames, guardian.user.username], usernames: [*params[:target_usernames], guardian.user.username],
groups: contract.target_groups, groups: params[:target_groups],
) )
end end
@ -77,11 +77,11 @@ module Chat
!user_comm_screener.actor_disallowing_all_pms? !user_comm_screener.actor_disallowing_all_pms?
end end
def fetch_or_create_direct_message(target_users:, contract:) def fetch_or_create_direct_message(target_users:, params:)
ids = target_users.map(&:id) ids = target_users.map(&:id)
is_group = ids.size > 2 || contract.name.present? is_group = ids.size > 2 || params[:name].present?
if contract.upsert || !is_group if params[:upsert] || !is_group
::Chat::DirectMessage.for_user_ids(ids, group: is_group) || ::Chat::DirectMessage.for_user_ids(ids, group: is_group) ||
::Chat::DirectMessage.create(user_ids: ids, group: is_group) ::Chat::DirectMessage.create(user_ids: ids, group: is_group)
else else
@ -93,8 +93,8 @@ module Chat
::Chat::DirectMessageChannel.find_or_create_by(chatable: direct_message) ::Chat::DirectMessageChannel.find_or_create_by(chatable: direct_message)
end end
def set_optional_name(channel:, contract:) def set_optional_name(channel:, params:)
channel.update!(name: contract.name) if contract.name&.length&.positive? channel.update!(params.slice(:name)) if params[:name]&.size&.positive?
end end
def update_memberships(channel:, target_users:) def update_memberships(channel:, target_users:)

View File

@ -34,7 +34,7 @@ module Chat
end end
policy :no_silenced_user policy :no_silenced_user
contract do params do
attribute :chat_channel_id, :string attribute :chat_channel_id, :string
attribute :in_reply_to_id, :string attribute :in_reply_to_id, :string
attribute :context_topic_id, :integer attribute :context_topic_id, :integer
@ -80,8 +80,8 @@ module Chat
!guardian.is_silenced? !guardian.is_silenced?
end end
def fetch_channel(contract:) def fetch_channel(params:)
Chat::Channel.find_by_id_or_slug(contract.chat_channel_id) Chat::Channel.find_by_id_or_slug(params[:chat_channel_id])
end end
def enforce_membership(guardian:, channel:, options:) def enforce_membership(guardian:, channel:, options:)
@ -98,17 +98,17 @@ module Chat
channel.membership_for(guardian.user) channel.membership_for(guardian.user)
end end
def fetch_reply(contract:) def fetch_reply(params:)
Chat::Message.find_by(id: contract.in_reply_to_id) Chat::Message.find_by(id: params[:in_reply_to_id])
end end
def ensure_reply_consistency(channel:, contract:, reply:) def ensure_reply_consistency(channel:, params:, reply:)
return true if contract.in_reply_to_id.blank? return true if params[:in_reply_to_id].blank?
reply&.chat_channel == channel reply&.chat_channel == channel
end end
def fetch_thread(contract:, reply:, channel:, options:) def fetch_thread(params:, reply:, channel:, options:)
return Chat::Thread.find_by(id: contract.thread_id) if contract.thread_id.present? return Chat::Thread.find_by(id: params[:thread_id]) if params[:thread_id].present?
return unless reply return unless reply
reply.thread || reply.thread ||
reply.build_thread( reply.build_thread(
@ -119,8 +119,8 @@ module Chat
) )
end end
def ensure_valid_thread_for_channel(thread:, contract:, channel:) def ensure_valid_thread_for_channel(thread:, params:, channel:)
return true if contract.thread_id.blank? return true if params[:thread_id].blank?
thread&.channel == channel thread&.channel == channel
end end
@ -129,29 +129,28 @@ module Chat
reply.thread == thread reply.thread == thread
end end
def fetch_uploads(contract:, guardian:) def fetch_uploads(params:, guardian:)
return [] if !SiteSetting.chat_allow_uploads return [] if !SiteSetting.chat_allow_uploads
guardian.user.uploads.where(id: contract.upload_ids) guardian.user.uploads.where(id: params[:upload_ids])
end end
def clean_message(contract:, options:) def clean_message(params:, options:)
contract.message = params[:message] = TextCleaner.clean(
TextCleaner.clean( params[:message],
contract.message,
strip_whitespaces: options.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:, options:) def instantiate_message(channel:, guardian:, params:, 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,
in_reply_to: reply, in_reply_to: reply,
message: contract.message, message: params[:message],
uploads: uploads, uploads: uploads,
thread: thread, thread: thread,
cooked: ::Chat::Message.cook(contract.message, user_id: guardian.user.id), cooked: ::Chat::Message.cook(params[:message], user_id: guardian.user.id),
cooked_version: ::Chat::Message::BAKED_VERSION, cooked_version: ::Chat::Message::BAKED_VERSION,
streaming: options.streaming, streaming: options.streaming,
) )
@ -199,14 +198,14 @@ module Chat
Chat::Action::PublishAndFollowDirectMessageChannel.call(channel_membership: membership) Chat::Action::PublishAndFollowDirectMessageChannel.call(channel_membership: membership)
end end
def publish_new_thread(reply:, contract:, channel:, thread:) def publish_new_thread(reply:, channel:, thread:)
return unless channel.threading_enabled? || thread&.force return unless channel.threading_enabled? || thread&.force
return unless reply&.thread_id_previously_changed?(from: nil) return unless reply&.thread_id_previously_changed?(from: nil)
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:, options:) def process(channel:, message_instance:, params:, thread:, options:)
::Chat::Publisher.publish_new!(channel, message_instance, contract.staged_id) ::Chat::Publisher.publish_new!(channel, message_instance, params[:staged_id])
DiscourseEvent.trigger( DiscourseEvent.trigger(
:chat_message_created, :chat_message_created,
@ -217,20 +216,20 @@ module Chat
thread: thread, thread: thread,
thread_replies_count: thread&.replies_count_cache || 0, thread_replies_count: thread&.replies_count_cache || 0,
context: { context: {
post_ids: contract.context_post_ids, post_ids: params[:context_post_ids],
topic_id: contract.context_topic_id, topic_id: params[:context_topic_id],
}, },
}, },
) )
if options.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: params[:staged_id] },
) )
else else
Jobs.enqueue( Jobs.enqueue(
Jobs::Chat::ProcessMessage, Jobs::Chat::ProcessMessage,
{ chat_message_id: message_instance.id, staged_id: contract.staged_id }, { chat_message_id: message_instance.id, staged_id: params[:staged_id] },
) )
end end
end end

View File

@ -17,7 +17,7 @@ module Chat
# @option params [String,nil] :title # @option params [String,nil] :title
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :original_message_id, :integer attribute :original_message_id, :integer
attribute :channel_id, :integer attribute :channel_id, :integer
attribute :title, :string attribute :title, :string
@ -39,14 +39,14 @@ module Chat
private private
def fetch_channel(contract:) def fetch_channel(params:)
::Chat::Channel.find_by(id: contract.channel_id) ::Chat::Channel.find_by(id: params[:channel_id])
end end
def fetch_original_message(channel:, contract:) def fetch_original_message(channel:, params:)
::Chat::Message.find_by( ::Chat::Message.find_by(
id: contract.original_message_id, id: params[:original_message_id],
chat_channel_id: contract.channel_id, chat_channel_id: params[:channel_id],
) )
end end
@ -58,33 +58,33 @@ module Chat
channel.threading_enabled channel.threading_enabled
end end
def find_or_create_thread(channel:, original_message:, contract:) def find_or_create_thread(channel:, original_message:, params:)
if original_message.thread_id.present? if original_message.thread_id.present?
return context[:thread] = ::Chat::Thread.find_by(id: original_message.thread_id) return context[:thread] = ::Chat::Thread.find_by(id: original_message.thread_id)
end end
context[:thread] = channel.threads.create( context[:thread] = channel.threads.create(
title: contract.title, title: params[:title],
original_message: original_message, original_message: original_message,
original_message_user: original_message.user, original_message_user: original_message.user,
) )
fail!(context.thread.errors.full_messages.join(", ")) if context.thread.invalid? fail!(context.thread.errors.full_messages.join(", ")) if context.thread.invalid?
end end
def associate_thread_to_message(original_message:) def associate_thread_to_message(original_message:, thread:)
original_message.update(thread: context.thread) original_message.update(thread:)
end end
def fetch_membership(guardian:) def fetch_membership(guardian:, thread:)
context[:membership] = context.thread.membership_for(guardian.user) context[:membership] = thread.membership_for(guardian.user)
end end
def publish_new_thread(channel:, original_message:) def publish_new_thread(channel:, original_message:, thread:)
::Chat::Publisher.publish_thread_created!(channel, original_message, context.thread.id) ::Chat::Publisher.publish_thread_created!(channel, original_message, thread.id)
end end
def trigger_chat_thread_created_event def trigger_chat_thread_created_event(thread:)
::DiscourseEvent.trigger(:chat_thread_created, context.thread) ::DiscourseEvent.trigger(:chat_thread_created, thread)
end end
end end
end end

View File

@ -27,7 +27,7 @@ module Chat
# @option params [Boolean] :queue_for_review (optional) Adds a special reason to the reviewable score and creates the reviewable using the force_review option. # @option params [Boolean] :queue_for_review (optional) Adds a special reason to the reviewable score and creates the reviewable using the force_review option.
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :message_id, :integer attribute :message_id, :integer
attribute :channel_id, :integer attribute :channel_id, :integer
attribute :flag_type_id, :integer attribute :flag_type_id, :integer
@ -46,36 +46,29 @@ module Chat
private private
def fetch_message(contract:) def fetch_message(params:)
Chat::Message.includes(:chat_channel, :revisions).find_by( Chat::Message.includes(:chat_channel, :revisions).find_by(
id: contract.message_id, id: params[:message_id],
chat_channel_id: contract.channel_id, chat_channel_id: params[:channel_id],
) )
end end
def can_flag_message_in_channel(guardian:, contract:, message:) def can_flag_message_in_channel(guardian:, params:, message:)
guardian.can_join_chat_channel?(message.chat_channel) && guardian.can_join_chat_channel?(message.chat_channel) &&
guardian.can_flag_chat_message?(message) && guardian.can_flag_chat_message?(message) &&
guardian.can_flag_message_as?( guardian.can_flag_message_as?(
message, message,
contract.flag_type_id, params[:flag_type_id],
{ params.slice(:queue_for_review, :take_action, :is_warning),
queue_for_review: contract.queue_for_review,
take_action: contract.take_action,
is_warning: contract.is_warning,
},
) )
end end
def flag_message(message:, contract:, guardian:) def flag_message(message:, params:, guardian:)
Chat::ReviewQueue.new.flag_message( Chat::ReviewQueue.new.flag_message(
message, message,
guardian, guardian,
contract.flag_type_id, params[:flag_type_id],
message: contract.message, **params.slice(:message, :is_warning, :take_action, :queue_for_review),
is_warning: contract.is_warning,
take_action: contract.take_action,
queue_for_review: contract.queue_for_review,
) )
end end
end end

View File

@ -17,7 +17,7 @@ module Chat
# @option params [Integer, nil] :message_id # @option params [Integer, nil] :message_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :user_ids, :array attribute :user_ids, :array
attribute :channel_id, :integer attribute :channel_id, :integer
attribute :message_id, :integer attribute :message_id, :integer
@ -32,24 +32,24 @@ module Chat
private private
def fetch_channel(contract:) def fetch_channel(params:)
::Chat::Channel.find_by(id: contract.channel_id) ::Chat::Channel.find_by(id: params[:channel_id])
end end
def can_view_channel(guardian:, channel:) def can_view_channel(guardian:, channel:)
guardian.can_preview_chat_channel?(channel) guardian.can_preview_chat_channel?(channel)
end end
def fetch_users(contract:) def fetch_users(params:)
::User ::User
.joins(:user_option) .joins(:user_option)
.where(user_options: { chat_enabled: true }) .where(user_options: { chat_enabled: true })
.not_suspended .not_suspended
.where(id: contract.user_ids) .where(id: params[:user_ids])
.limit(50) .limit(50)
end end
def send_invite_notifications(channel:, guardian:, users:, contract:) def send_invite_notifications(channel:, guardian:, users:, params:)
users&.each do |invited_user| users&.each do |invited_user|
next if !invited_user.guardian.can_join_chat_channel?(channel) next if !invited_user.guardian.can_join_chat_channel?(channel)
@ -59,8 +59,8 @@ module Chat
chat_channel_title: channel.title(invited_user), chat_channel_title: channel.title(invited_user),
chat_channel_slug: channel.slug, chat_channel_slug: channel.slug,
invited_by_username: guardian.user.username, invited_by_username: guardian.user.username,
} chat_message_id: params[:message_id],
data[:chat_message_id] = contract.message_id if contract.message_id }.compact
invited_user.notifications.create( invited_user.notifications.create(
notification_type: ::Notification.types[:chat_invitation], notification_type: ::Notification.types[:chat_invitation],

View File

@ -20,7 +20,7 @@ module Chat
# @option params [Integer] :channel_id ID of the channel # @option params [Integer] :channel_id ID of the channel
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :channel_id, :integer attribute :channel_id, :integer
validates :channel_id, presence: true validates :channel_id, presence: true
@ -31,8 +31,8 @@ module Chat
private private
def fetch_channel(contract:) def fetch_channel(params:)
Chat::Channel.find_by(id: contract.channel_id) Chat::Channel.find_by(id: params[:channel_id])
end end
def leave(channel:, guardian:) def leave(channel:, guardian:)

View File

@ -16,7 +16,7 @@ module Chat
# @option params [Integer] :channel_id # @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :channel_id, :integer attribute :channel_id, :integer
attribute :page_size, :integer attribute :page_size, :integer
@ -56,8 +56,8 @@ module Chat
private private
def fetch_channel(contract:) def fetch_channel(params:)
::Chat::Channel.includes(:chatable).find_by(id: contract.channel_id) ::Chat::Channel.includes(:chatable).find_by(id: params[:channel_id])
end end
def fetch_membership(channel:, guardian:) def fetch_membership(channel:, guardian:)
@ -72,11 +72,11 @@ module Chat
guardian.can_preview_chat_channel?(channel) guardian.can_preview_chat_channel?(channel)
end end
def determine_target_message_id(contract:, membership:) def determine_target_message_id(params:, membership:)
if contract.fetch_from_last_read if params[:fetch_from_last_read]
context[:target_message_id] = membership&.last_read_message_id context[:target_message_id] = membership&.last_read_message_id
else else
context[:target_message_id] = contract.target_message_id context[:target_message_id] = params[:target_message_id]
end end
end end
@ -96,16 +96,16 @@ module Chat
true true
end end
def fetch_messages(channel:, contract:, guardian:, enabled_threads:) def fetch_messages(channel:, params:, guardian:, enabled_threads:, target_message_id:)
messages_data = messages_data =
::Chat::MessagesQuery.call( ::Chat::MessagesQuery.call(
channel: channel, channel:,
guardian: guardian, guardian:,
target_message_id: context.target_message_id, target_message_id:,
include_thread_messages: !enabled_threads, include_thread_messages: !enabled_threads,
page_size: contract.page_size || Chat::MessagesQuery::MAX_PAGE_SIZE, page_size: params[:page_size] || Chat::MessagesQuery::MAX_PAGE_SIZE,
direction: contract.direction, direction: params[:direction],
target_date: contract.target_date, target_date: params[:target_date],
) )
context[:can_load_more_past] = messages_data[:can_load_more_past] context[:can_load_more_past] = messages_data[:can_load_more_past]

View File

@ -16,7 +16,7 @@ module Chat
# @option params [Integer] :thread_id # @option params [Integer] :thread_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :thread_id, :integer attribute :thread_id, :integer
# If this is not present, then we just fetch messages with page_size # If this is not present, then we just fetch messages with page_size
# and direction. # and direction.
@ -50,8 +50,8 @@ module Chat
private private
def fetch_thread(contract:) def fetch_thread(params:)
::Chat::Thread.strict_loading.includes(channel: :chatable).find_by(id: contract.thread_id) ::Chat::Thread.strict_loading.includes(channel: :chatable).find_by(id: params[:thread_id])
end end
def can_view_thread(guardian:, thread:) def can_view_thread(guardian:, thread:)
@ -62,42 +62,42 @@ module Chat
thread.membership_for(guardian.user) thread.membership_for(guardian.user)
end end
def determine_target_message_id(contract:, membership:, guardian:, thread:) def determine_target_message_id(params:, membership:, guardian:, thread:)
if contract.fetch_from_last_message if params[:fetch_from_last_message]
context[:target_message_id] = thread.last_message_id context[:target_message_id] = thread.last_message_id
elsif contract.fetch_from_first_message elsif params[:fetch_from_first_message]
context[:target_message_id] = thread.original_message_id context[:target_message_id] = thread.original_message_id
elsif contract.fetch_from_last_read || !contract.target_message_id elsif params[:fetch_from_last_read] || !params[:target_message_id]
context[:target_message_id] = membership&.last_read_message_id context[:target_message_id] = membership&.last_read_message_id
elsif contract.target_message_id elsif params[:target_message_id]
context[:target_message_id] = contract.target_message_id context[:target_message_id] = params[:target_message_id]
end end
end end
def target_message_exists(contract:, guardian:) def target_message_exists(params:, guardian:)
return true if context.target_message_id.blank? return true if context.target_message_id.blank?
target_message = target_message =
::Chat::Message.with_deleted.find_by( ::Chat::Message.with_deleted.find_by(
id: context.target_message_id, id: context.target_message_id,
thread_id: contract.thread_id, thread_id: params[:thread_id],
) )
return false if target_message.blank? return false if target_message.blank?
return true if !target_message.trashed? return true if !target_message.trashed?
target_message.user_id == guardian.user.id || guardian.is_staff? target_message.user_id == guardian.user.id || guardian.is_staff?
end end
def fetch_messages(thread:, guardian:, contract:) def fetch_messages(thread:, guardian:, params:)
messages_data = messages_data =
::Chat::MessagesQuery.call( ::Chat::MessagesQuery.call(
channel: thread.channel, channel: thread.channel,
guardian: guardian, guardian: guardian,
target_message_id: context.target_message_id, target_message_id: context.target_message_id,
thread_id: thread.id, thread_id: thread.id,
page_size: contract.page_size || Chat::MessagesQuery::MAX_PAGE_SIZE, page_size: params[:page_size] || Chat::MessagesQuery::MAX_PAGE_SIZE,
direction: contract.direction, direction: params[:direction],
target_date: contract.target_date, target_date: params[:target_date],
include_target_message_id: include_target_message_id:
contract.fetch_from_first_message || contract.fetch_from_last_message, params[:fetch_from_first_message] || params[:fetch_from_last_message],
) )
context[:can_load_more_past] = messages_data[:can_load_more_past] context[:can_load_more_past] = messages_data[:can_load_more_past]

View File

@ -25,7 +25,7 @@ module Chat
# @option params [Integer] :offset # @option params [Integer] :offset
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :channel_id, :integer attribute :channel_id, :integer
attribute :limit, :integer attribute :limit, :integer
attribute :offset, :integer attribute :offset, :integer
@ -51,16 +51,16 @@ module Chat
private private
def set_limit(contract:) def set_limit(params:)
context[:limit] = (contract.limit || THREADS_LIMIT).to_i.clamp(1, THREADS_LIMIT) context[:limit] = (params[:limit] || THREADS_LIMIT).to_i.clamp(1, THREADS_LIMIT)
end end
def set_offset(contract:) def set_offset(params:)
context[:offset] = [contract.offset || 0, 0].max context[:offset] = [params[:offset] || 0, 0].max
end end
def fetch_channel(contract:) def fetch_channel(params:)
::Chat::Channel.strict_loading.includes(:chatable).find_by(id: contract.channel_id) ::Chat::Channel.strict_loading.includes(:chatable).find_by(id: params[:channel_id])
end end
def threading_enabled_for_channel(channel:) def threading_enabled_for_channel(channel:)
@ -137,10 +137,10 @@ module Chat
context[:participants] = ::Chat::ThreadParticipantQuery.call(thread_ids: threads.map(&:id)) context[:participants] = ::Chat::ThreadParticipantQuery.call(thread_ids: threads.map(&:id))
end end
def build_load_more_url(contract:) def build_load_more_url(channel:)
load_more_params = { offset: context.offset + context.limit }.to_query load_more_params = { offset: context.offset + context.limit }.to_query
context[:load_more_url] = ::URI::HTTP.build( context[:load_more_url] = ::URI::HTTP.build(
path: "/chat/api/channels/#{contract.channel_id}/threads", path: "/chat/api/channels/#{channel.id}/threads",
query: load_more_params, query: load_more_params,
).request_uri ).request_uri
end end

View File

@ -17,7 +17,7 @@ module Chat
# @option params [Integer] :channel_id # @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :thread_id, :integer attribute :thread_id, :integer
attribute :channel_id, :integer attribute :channel_id, :integer
@ -31,12 +31,12 @@ module Chat
private private
def fetch_thread(contract:) def fetch_thread(params:)
Chat::Thread.includes( Chat::Thread.includes(
:channel, :channel,
original_message_user: :user_status, original_message_user: :user_status,
original_message: :chat_webhook_event, original_message: :chat_webhook_event,
).find_by(id: contract.thread_id, channel_id: contract.channel_id) ).find_by(id: params[:thread_id], channel_id: params[:channel_id])
end end
def invalid_access(guardian:, thread:) def invalid_access(guardian:, thread:)

View File

@ -21,7 +21,7 @@ module Chat
# @option params [Integer] :offset # @option params [Integer] :offset
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :limit, :integer attribute :limit, :integer
attribute :offset, :integer attribute :offset, :integer
end end
@ -35,12 +35,12 @@ module Chat
private private
def set_limit(contract:) def set_limit(params:)
context[:limit] = (contract.limit || THREADS_LIMIT).to_i.clamp(1, THREADS_LIMIT) context[:limit] = (params[:limit] || THREADS_LIMIT).to_i.clamp(1, THREADS_LIMIT)
end end
def set_offset(contract:) def set_offset(params:)
context[:offset] = [contract.offset || 0, 0].max context[:offset] = [params[:offset] || 0, 0].max
end end
def fetch_threads(guardian:) def fetch_threads(guardian:)
@ -131,7 +131,7 @@ module Chat
context[:participants] = ::Chat::ThreadParticipantQuery.call(thread_ids: threads.map(&:id)) context[:participants] = ::Chat::ThreadParticipantQuery.call(thread_ids: threads.map(&:id))
end end
def build_load_more_url(contract:) def build_load_more_url
load_more_params = { limit: context.limit, offset: context.offset + context.limit }.to_query load_more_params = { limit: context.limit, offset: context.offset + context.limit }.to_query
context[:load_more_url] = ::URI::HTTP.build( context[:load_more_url] = ::URI::HTTP.build(

View File

@ -24,7 +24,7 @@ module Chat
# @option params [Integer] :channel_id # @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :thread_id, :integer attribute :thread_id, :integer
attribute :channel_id, :integer attribute :channel_id, :integer
@ -37,8 +37,8 @@ module Chat
private private
def fetch_thread(contract:) def fetch_thread(params:)
Chat::Thread.find_by(id: contract.thread_id, channel_id: contract.channel_id) Chat::Thread.find_by(id: params[:thread_id], channel_id: params[:channel_id])
end end
def can_view_channel(guardian:, thread:) def can_view_channel(guardian:, thread:)
@ -49,7 +49,7 @@ module Chat
thread.channel.threading_enabled thread.channel.threading_enabled
end end
def create_or_update_membership(thread:, guardian:, contract:) def create_or_update_membership(thread:, guardian:)
membership = thread.membership_for(guardian.user) membership = thread.membership_for(guardian.user)
membership = membership =
thread.add( thread.add(

View File

@ -18,7 +18,7 @@ module Chat
# @option params [Integer] :channel_id # @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :message_id, :integer attribute :message_id, :integer
attribute :channel_id, :integer attribute :channel_id, :integer
@ -36,11 +36,11 @@ module Chat
private private
def fetch_message(contract:) def fetch_message(params:)
Chat::Message Chat::Message
.with_deleted .with_deleted
.includes(chat_channel: :chatable) .includes(chat_channel: :chatable)
.find_by(id: contract.message_id, chat_channel_id: contract.channel_id) .find_by(id: params[:message_id], chat_channel_id: params[:channel_id])
end end
def invalid_access(guardian:, message:) def invalid_access(guardian:, message:)

View File

@ -17,7 +17,7 @@ module Chat
# @option params [String] :term # @option params [String] :term
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :term, :string, default: "" attribute :term, :string, default: ""
attribute :include_users, :boolean, default: true attribute :include_users, :boolean, default: true
attribute :include_groups, :boolean, default: true attribute :include_groups, :boolean, default: true
@ -34,42 +34,42 @@ module Chat
private private
def clean_term(contract:) def clean_term(params:)
contract.term = contract.term&.downcase&.strip&.gsub(/^[@#]+/, "") params[:term] = params[:term]&.downcase&.strip&.gsub(/^[@#]+/, "")
end end
def fetch_memberships(guardian:) def fetch_memberships(guardian:)
::Chat::ChannelMembershipManager.all_for_user(guardian.user) ::Chat::ChannelMembershipManager.all_for_user(guardian.user)
end end
def fetch_users(guardian:, contract:) def fetch_users(guardian:, params:)
return unless contract.include_users return unless params[:include_users]
return unless guardian.can_create_direct_message? return unless guardian.can_create_direct_message?
search_users(contract, guardian) search_users(params, guardian)
end end
def fetch_groups(guardian:, contract:) def fetch_groups(guardian:, params:)
return unless contract.include_groups return unless params[:include_groups]
return unless guardian.can_create_direct_message? return unless guardian.can_create_direct_message?
search_groups(contract, guardian) search_groups(params, guardian)
end end
def fetch_category_channels(guardian:, contract:) def fetch_category_channels(guardian:, params:)
return unless contract.include_category_channels return unless params[:include_category_channels]
return unless SiteSetting.enable_public_channels return unless SiteSetting.enable_public_channels
search_category_channels(contract, guardian) search_category_channels(params, guardian)
end end
def fetch_direct_message_channels(guardian:, contract:, users:) def fetch_direct_message_channels(guardian:, params:, users:)
return unless contract.include_direct_message_channels return unless params[:include_direct_message_channels]
return unless guardian.can_create_direct_message? return unless guardian.can_create_direct_message?
search_direct_message_channels(guardian, contract, users) search_direct_message_channels(guardian, params, users)
end end
def search_users(contract, guardian) def search_users(params, guardian)
user_search = ::UserSearch.new(contract.term, limit: SEARCH_RESULT_LIMIT) user_search = ::UserSearch.new(params[:term], limit: SEARCH_RESULT_LIMIT)
if contract.term.blank? if params[:term].blank?
user_search = user_search.scoped_users user_search = user_search.scoped_users
else else
user_search = user_search.search user_search = user_search.search
@ -81,51 +81,51 @@ module Chat
user_search = user_search.real(allowed_bot_user_ids: allowed_bot_user_ids) user_search = user_search.real(allowed_bot_user_ids: allowed_bot_user_ids)
user_search = user_search.includes(:user_option) user_search = user_search.includes(:user_option)
if contract.excluded_memberships_channel_id if params[:excluded_memberships_channel_id]
user_search = user_search =
user_search.where( user_search.where(
"NOT EXISTS (SELECT 1 FROM user_chat_channel_memberships WHERE user_id = users.id AND chat_channel_id = ?)", "NOT EXISTS (SELECT 1 FROM user_chat_channel_memberships WHERE user_id = users.id AND chat_channel_id = ?)",
contract.excluded_memberships_channel_id, params[:excluded_memberships_channel_id],
) )
end end
user_search user_search
end end
def search_groups(contract, guardian) def search_groups(params, guardian)
Group Group
.visible_groups(guardian.user) .visible_groups(guardian.user)
.includes(users: :user_option) .includes(users: :user_option)
.where( .where(
"groups.name ILIKE :term_like OR groups.full_name ILIKE :term_like", "groups.name ILIKE :term_like OR groups.full_name ILIKE :term_like",
term_like: "%#{contract.term}%", term_like: "%#{params[:term]}%",
) )
.limit(SEARCH_RESULT_LIMIT) .limit(SEARCH_RESULT_LIMIT)
end end
def search_category_channels(contract, guardian) def search_category_channels(params, guardian)
::Chat::ChannelFetcher.secured_public_channel_search( ::Chat::ChannelFetcher.secured_public_channel_search(
guardian, guardian,
status: :open, status: :open,
filter: contract.term, filter: params[:term],
filter_on_category_name: false, filter_on_category_name: false,
match_filter_on_starts_with: false, match_filter_on_starts_with: false,
limit: SEARCH_RESULT_LIMIT, limit: SEARCH_RESULT_LIMIT,
) )
end end
def search_direct_message_channels(guardian, contract, users) def search_direct_message_channels(guardian, params, users)
channels = channels =
::Chat::ChannelFetcher.secured_direct_message_channels_search( ::Chat::ChannelFetcher.secured_direct_message_channels_search(
guardian.user.id, guardian.user.id,
guardian, guardian,
filter: contract.term, filter: params[:term],
match_filter_on_starts_with: false, match_filter_on_starts_with: false,
limit: SEARCH_RESULT_LIMIT, limit: SEARCH_RESULT_LIMIT,
) || [] ) || []
# skip 1:1s when search returns users # skip 1:1s when search returns users
if contract.include_users && users.present? if params[:include_users] && users.present?
channels.reject! do |channel| channels.reject! do |channel|
other_user_ids = channel.allowed_user_ids - [guardian.user.id] other_user_ids = channel.allowed_user_ids - [guardian.user.id]
other_user_ids.size <= 1 other_user_ids.size <= 1

View File

@ -14,7 +14,7 @@ module Chat
# @param [Hash] params # @param [Hash] params
# @option params [Integer] :message_id # @option params [Integer] :message_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :message_id, :integer attribute :message_id, :integer
validates :message_id, presence: true validates :message_id, presence: true
@ -28,8 +28,8 @@ module Chat
private private
def fetch_message(contract:) def fetch_message(params:)
::Chat::Message.find_by(id: contract.message_id) ::Chat::Message.find_by(id: params[:message_id])
end end
def enforce_membership(guardian:, message:) def enforce_membership(guardian:, message:)
@ -49,7 +49,7 @@ module Chat
message.update!(streaming: false) message.update!(streaming: false)
end end
def publish_message_streaming_state(guardian:, message:, contract:) def publish_message_streaming_state(guardian:, message:)
::Chat::Publisher.publish_edit!(message.chat_channel, message) ::Chat::Publisher.publish_edit!(message.chat_channel, message)
end end
end end

View File

@ -34,7 +34,7 @@ module Chat
# @option params [Integer] :channel_ids # @option params [Integer] :channel_ids
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :channel_ids, :array, default: [] attribute :channel_ids, :array, default: []
attribute :thread_ids, :array, default: [] attribute :thread_ids, :array, default: []
attribute :include_missing_memberships, default: false attribute :include_missing_memberships, default: false
@ -45,14 +45,16 @@ module Chat
private private
def fetch_report(contract:, guardian:) def fetch_report(params:, guardian:)
::Chat::TrackingStateReportQuery.call( ::Chat::TrackingStateReportQuery.call(
guardian: guardian, guardian:,
channel_ids: contract.channel_ids, **params.slice(
thread_ids: contract.thread_ids, :channel_ids,
include_missing_memberships: contract.include_missing_memberships, :thread_ids,
include_threads: contract.include_threads, :include_missing_memberships,
include_read: contract.include_read, :include_threads,
:include_read,
),
) )
end end
end end

View File

@ -18,7 +18,7 @@ module Chat
# @option params [Integer] :channel_id # @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :message_id, :integer attribute :message_id, :integer
attribute :channel_id, :integer attribute :channel_id, :integer
@ -38,10 +38,10 @@ module Chat
private private
def fetch_message(contract:) def fetch_message(params:)
Chat::Message.includes(chat_channel: :chatable).find_by( Chat::Message.includes(chat_channel: :chatable).find_by(
id: contract.message_id, id: params[:message_id],
chat_channel_id: contract.channel_id, chat_channel_id: params[:channel_id],
) )
end end
@ -79,7 +79,7 @@ module Chat
message.chat_channel.update_last_message_id! message.chat_channel.update_last_message_id!
end end
def publish_events(contract:, guardian:, message:) def publish_events(guardian:, message:)
DiscourseEvent.trigger(:chat_message_trashed, message, message.chat_channel, guardian.user) DiscourseEvent.trigger(:chat_message_trashed, message, message.chat_channel, guardian.user)
Chat::Publisher.publish_delete!(message.chat_channel, message) Chat::Publisher.publish_delete!(message.chat_channel, message)

View File

@ -18,7 +18,7 @@ module Chat
# @option params [Integer] :channel_id # @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :channel_id, :integer attribute :channel_id, :integer
attribute :message_ids, :array attribute :message_ids, :array
@ -38,10 +38,10 @@ module Chat
private private
def fetch_messages(contract:) def fetch_messages(params:)
Chat::Message.includes(chat_channel: :chatable).where( Chat::Message.includes(chat_channel: :chatable).where(
id: contract.message_ids, id: params[:message_ids],
chat_channel_id: contract.channel_id, chat_channel_id: params[:channel_id],
) )
end end
@ -86,11 +86,11 @@ module Chat
messages.each { |message| message.thread&.decrement_replies_count_cache } messages.each { |message| message.thread&.decrement_replies_count_cache }
end end
def publish_events(contract:, guardian:, messages:) def publish_events(params:, guardian:, messages:)
messages.each do |message| messages.each do |message|
DiscourseEvent.trigger(:chat_message_trashed, message, message.chat_channel, guardian.user) DiscourseEvent.trigger(:chat_message_trashed, message, message.chat_channel, guardian.user)
end end
Chat::Publisher.publish_bulk_delete!(messages.first.chat_channel, contract.message_ids) Chat::Publisher.publish_bulk_delete!(messages.first.chat_channel, params[:message_ids])
end end
end end
end end

View File

@ -20,7 +20,7 @@ module Chat
# @option params [Integer] :channel_id ID of the channel # @option params [Integer] :channel_id ID of the channel
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :channel_id, :integer attribute :channel_id, :integer
validates :channel_id, presence: true validates :channel_id, presence: true
@ -30,8 +30,8 @@ module Chat
private private
def fetch_channel(contract:) def fetch_channel(params:)
Chat::Channel.find_by(id: contract.channel_id) Chat::Channel.find_by(id: params[:channel_id])
end end
def unfollow(channel:, guardian:) def unfollow(channel:, guardian:)

View File

@ -36,7 +36,7 @@ module Chat
model :channel model :channel
policy :check_channel_permission policy :check_channel_permission
contract(default_values_from: :channel) do params(default_values_from: :channel) do
attribute :name, :string attribute :name, :string
attribute :description, :string attribute :description, :string
attribute :slug, :string attribute :slug, :string
@ -66,8 +66,8 @@ module Chat
guardian.can_preview_chat_channel?(channel) && guardian.can_edit_chat_channel?(channel) guardian.can_preview_chat_channel?(channel) && guardian.can_edit_chat_channel?(channel)
end end
def update_channel(channel:, contract:) def update_channel(channel:, params:)
channel.update!(contract.attributes) channel.update!(**params)
end end
def mark_all_threads_as_read_if_needed(channel:) def mark_all_threads_as_read_if_needed(channel:)

View File

@ -16,8 +16,8 @@ module Chat
# @option params [String] :status # @option params [String] :status
# @return [Service::Base::Context] # @return [Service::Base::Context]
model :channel, :fetch_channel model :channel
contract do params do
attribute :status, :string attribute :status, :string
validates :status, inclusion: { in: Chat::Channel.editable_statuses.keys } validates :status, inclusion: { in: Chat::Channel.editable_statuses.keys }
@ -31,13 +31,13 @@ module Chat
Chat::Channel.find_by(id: params[: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:, params:)
guardian.can_preview_chat_channel?(channel) && guardian.can_preview_chat_channel?(channel) &&
guardian.can_change_channel_status?(channel, contract.status.to_sym) guardian.can_change_channel_status?(channel, params[:status].to_sym)
end end
def change_status(channel:, contract:, guardian:) def change_status(channel:, params:, guardian:)
channel.public_send("#{contract.status}!", guardian.user) channel.public_send("#{params[:status]}!", guardian.user)
end end
end end
end end

View File

@ -26,7 +26,7 @@ module Chat
attribute :process_inline, :boolean, default: -> { Rails.env.test? } attribute :process_inline, :boolean, default: -> { Rails.env.test? }
end end
contract do params do
attribute :message_id, :string attribute :message_id, :string
attribute :message, :string attribute :message, :string
attribute :upload_ids, :array attribute :upload_ids, :array
@ -55,7 +55,7 @@ module Chat
message.chat_channel.add(guardian.user) if guardian.user.bot? message.chat_channel.add(guardian.user) if guardian.user.bot?
end end
def fetch_message(contract:) def fetch_message(params:)
::Chat::Message.includes( ::Chat::Message.includes(
:chat_mentions, :chat_mentions,
:bookmarks, :bookmarks,
@ -70,16 +70,16 @@ module Chat
chatable: [:topic_only_relative_url, direct_message_users: [user: :user_option]], chatable: [:topic_only_relative_url, direct_message_users: [user: :user_option]],
], ],
user: :user_status, user: :user_status,
).find_by(id: contract.message_id) ).find_by(id: params[:message_id])
end end
def fetch_membership(guardian:, message:) def fetch_membership(guardian:, message:)
message.chat_channel.membership_for(guardian.user) message.chat_channel.membership_for(guardian.user)
end end
def fetch_uploads(contract:, guardian:) def fetch_uploads(params:, guardian:)
return if !SiteSetting.chat_allow_uploads return if !SiteSetting.chat_allow_uploads
guardian.user.uploads.where(id: contract.upload_ids) guardian.user.uploads.where(id: params[:upload_ids])
end end
def can_modify_channel_message(guardian:, message:) def can_modify_channel_message(guardian:, message:)
@ -90,21 +90,20 @@ module Chat
guardian.can_edit_chat?(message) guardian.can_edit_chat?(message)
end end
def clean_message(contract:, options:) def clean_message(params:, options:)
contract.message = params[:message] = TextCleaner.clean(
TextCleaner.clean( params[:message],
contract.message,
strip_zero_width_spaces: true, strip_zero_width_spaces: true,
strip_whitespaces: options.strip_whitespaces, strip_whitespaces: options.strip_whitespaces,
) )
end end
def modify_message(contract:, message:, guardian:, uploads:) def modify_message(params:, message:, guardian:, uploads:)
message.message = contract.message message.message = params[:message]
message.last_editor_id = guardian.user.id message.last_editor_id = guardian.user.id
message.cook message.cook
return if uploads&.size != contract.upload_ids.to_a.size return if uploads&.size != params[:upload_ids].to_a.size
new_upload_ids = uploads.map(&:id) new_upload_ids = uploads.map(&:id)
existing_upload_ids = message.upload_ids existing_upload_ids = message.upload_ids
@ -157,7 +156,7 @@ module Chat
chars_edited > max_edited_chars chars_edited > max_edited_chars
end end
def publish(message:, guardian:, contract:, options:) def publish(message:, guardian:, 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)

View File

@ -19,7 +19,7 @@ module Chat
# @option params [Integer] :channel_id # @option params [Integer] :channel_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :thread_id, :integer attribute :thread_id, :integer
attribute :title, :string attribute :title, :string
@ -35,8 +35,8 @@ module Chat
private private
def fetch_thread(contract:) def fetch_thread(params:)
Chat::Thread.find_by(id: contract.thread_id) Chat::Thread.find_by(id: params[:thread_id])
end end
def can_view_channel(guardian:, thread:) def can_view_channel(guardian:, thread:)
@ -51,8 +51,8 @@ module Chat
thread.channel.threading_enabled thread.channel.threading_enabled
end end
def update(thread:, contract:) def update(thread:, params:)
thread.update(title: contract.title) thread.update(params.slice(:title))
fail!(thread.errors.full_messages.join(", ")) if thread.invalid? fail!(thread.errors.full_messages.join(", ")) if thread.invalid?
end end

View File

@ -26,7 +26,7 @@ module Chat
# @option params [Integer] :notification_level # @option params [Integer] :notification_level
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :thread_id, :integer attribute :thread_id, :integer
attribute :channel_id, :integer attribute :channel_id, :integer
attribute :notification_level, :integer attribute :notification_level, :integer
@ -44,8 +44,8 @@ module Chat
private private
def fetch_thread(contract:) def fetch_thread(params:)
Chat::Thread.find_by(id: contract.thread_id, channel_id: contract.channel_id) Chat::Thread.find_by(id: params[:thread_id], channel_id: params[:channel_id])
end end
def can_view_channel(guardian:, thread:) def can_view_channel(guardian:, thread:)
@ -56,13 +56,13 @@ module Chat
thread.channel.threading_enabled thread.channel.threading_enabled
end end
def create_or_update_membership(thread:, guardian:, contract:) def create_or_update_membership(thread:, guardian:, params:)
membership = thread.membership_for(guardian.user) membership = thread.membership_for(guardian.user)
if !membership if !membership
membership = thread.add(guardian.user) membership = thread.add(guardian.user)
membership.update!(last_read_message_id: thread.last_message_id) membership.update!(last_read_message_id: thread.last_message_id)
end end
membership.update!(notification_level: contract.notification_level) membership.update!(notification_level: params[:notification_level])
context[:membership] = membership context[:membership] = membership
end end
end end

View File

@ -16,7 +16,7 @@ module Chat
# @option params [Integer] :message_id # @option params [Integer] :message_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :message_id, :integer attribute :message_id, :integer
attribute :channel_id, :integer attribute :channel_id, :integer
@ -35,8 +35,8 @@ module Chat
private private
def fetch_channel(contract:) def fetch_channel(params:)
::Chat::Channel.find_by(id: contract.channel_id) ::Chat::Channel.find_by(id: params[:channel_id])
end end
def fetch_membership(guardian:, channel:) def fetch_membership(guardian:, channel:)
@ -47,8 +47,8 @@ module Chat
guardian.can_join_chat_channel?(membership.chat_channel) guardian.can_join_chat_channel?(membership.chat_channel)
end end
def fetch_message(channel:, contract:) def fetch_message(channel:, params:)
::Chat::Message.with_deleted.find_by(chat_channel_id: channel.id, id: contract.message_id) ::Chat::Message.with_deleted.find_by(chat_channel_id: channel.id, id: params[:message_id])
end end
def ensure_message_id_recency(message:, membership:) def ensure_message_id_recency(message:, membership:)

View File

@ -18,7 +18,7 @@ module Chat
# @option params [Integer] :message_id # @option params [Integer] :message_id
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :channel_id, :integer attribute :channel_id, :integer
attribute :thread_id, :integer attribute :thread_id, :integer
attribute :message_id, :integer attribute :message_id, :integer
@ -36,24 +36,24 @@ module Chat
private private
def fetch_thread(contract:) def fetch_thread(params:)
::Chat::Thread.find_by(id: contract.thread_id, channel_id: contract.channel_id) ::Chat::Thread.find_by(id: params[:thread_id], channel_id: params[:channel_id])
end end
def fetch_message(contract:, thread:) def invalid_access(guardian:, thread:)
::Chat::Message.with_deleted.find_by( guardian.can_join_chat_channel?(thread.channel)
id: contract.message_id || thread.last_message_id,
thread_id: contract.thread_id,
chat_channel_id: contract.channel_id,
)
end end
def fetch_membership(guardian:, thread:) def fetch_membership(guardian:, thread:)
thread.membership_for(guardian.user) thread.membership_for(guardian.user)
end end
def invalid_access(guardian:, thread:) def fetch_message(params:, thread:)
guardian.can_join_chat_channel?(thread.channel) ::Chat::Message.with_deleted.find_by(
id: params[:message_id] || thread.last_message_id,
thread: thread,
chat_channel: thread.channel,
)
end end
def ensure_valid_message(message:, membership:) def ensure_valid_message(message:, membership:)

View File

@ -24,12 +24,12 @@ module Chat
# @option params [Integer] :thread_id ID of the thread # @option params [Integer] :thread_id ID of the thread
# @return [Service::Base::Context] # @return [Service::Base::Context]
contract do params do
attribute :channel_id, :integer attribute :channel_id, :integer
validates :channel_id, presence: true
attribute :thread_id, :integer attribute :thread_id, :integer
attribute :data, :string attribute :data, :string
validates :channel_id, presence: true
end end
model :channel model :channel
policy :can_upsert_draft policy :can_upsert_draft
@ -38,36 +38,35 @@ module Chat
private private
def fetch_channel(contract:) def fetch_channel(params:)
Chat::Channel.find_by(id: contract.channel_id) Chat::Channel.find_by(id: params[:channel_id])
end end
def can_upsert_draft(guardian:, channel:) def can_upsert_draft(guardian:, channel:)
guardian.can_chat? && guardian.can_join_chat_channel?(channel) guardian.can_chat? && guardian.can_join_chat_channel?(channel)
end end
def check_thread_exists(contract:, channel:) def check_thread_exists(params:, channel:)
if contract.thread_id.present? return if params[:thread_id].blank?
fail!("Thread not found") if !channel.threads.exists?(id: contract.thread_id) fail!("Thread not found") if !channel.threads.exists?(id: params[:thread_id])
end
end end
def upsert_draft(contract:, guardian:) def upsert_draft(params:, guardian:)
if contract.data.present? if params[:data].present?
draft = draft =
Chat::Draft.find_or_initialize_by( Chat::Draft.find_or_initialize_by(
user_id: guardian.user.id, user_id: guardian.user.id,
chat_channel_id: contract.channel_id, chat_channel_id: params[:channel_id],
thread_id: contract.thread_id, thread_id: params[:thread_id],
) )
draft.data = contract.data draft.data = params[:data]
draft.save! draft.save!
else else
# when data is empty, we destroy the draft # when data is empty, we destroy the draft
Chat::Draft.where( Chat::Draft.where(
user: guardian.user, user: guardian.user,
chat_channel_id: contract.channel_id, chat_channel_id: params[:channel_id],
thread_id: contract.thread_id, thread_id: params[:thread_id],
).destroy_all ).destroy_all
end end
end end

View File

@ -1,14 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
RSpec.describe Chat::Action::CreateMembershipsForAutoJoin do RSpec.describe Chat::Action::CreateMembershipsForAutoJoin do
subject(:action) { described_class.call(channel: channel, contract: contract) } subject(:action) { described_class.call(channel:, params:) }
fab!(:channel) { Fabricate(:chat_channel, auto_join_users: true) } fab!(:channel) { Fabricate(:chat_channel, auto_join_users: true) }
fab!(:user_1) { Fabricate(:user, last_seen_at: 15.minutes.ago) } fab!(:user_1) { Fabricate(:user, last_seen_at: 15.minutes.ago) }
let(:start_user_id) { user_1.id } let(:start_user_id) { user_1.id }
let(:end_user_id) { user_1.id } let(:end_user_id) { user_1.id }
let(:contract) { OpenStruct.new(start_user_id: start_user_id, end_user_id: end_user_id) } let(:params) { OpenStruct.new(start_user_id: start_user_id, end_user_id: end_user_id) }
it "adds correct members" do it "adds correct members" do
expect(action).to eq([user_1.id]) expect(action).to eq([user_1.id])

View File

@ -47,10 +47,10 @@ RSpec.describe Chat::SearchChatable do
it "cleans the term" do it "cleans the term" do
params[:term] = "#bob" params[:term] = "#bob"
expect(result.contract.term).to eq("bob") expect(result.params[:term]).to eq("bob")
params[:term] = "@bob" params[:term] = "@bob"
expect(result.contract.term).to eq("bob") expect(result.params[:term]).to eq("bob")
end end
it "fetches user memberships" do it "fetches user memberships" do

View File

@ -38,7 +38,7 @@ RSpec.describe Service::Runner do
class FailedContractService class FailedContractService
include Service::Base include Service::Base
contract do params do
attribute :test attribute :test
validates :test, presence: true validates :test, presence: true
@ -48,7 +48,7 @@ RSpec.describe Service::Runner do
class SuccessContractService class SuccessContractService
include Service::Base include Service::Base
contract {} params {}
end end
class FailureWithModelService class FailureWithModelService

View File

@ -11,7 +11,7 @@ RSpec.describe Service::StepsInspector do
model :model model :model
policy :policy policy :policy
contract do params do
attribute :parameter attribute :parameter
validates :parameter, presence: true validates :parameter, presence: true
@ -45,7 +45,7 @@ RSpec.describe Service::StepsInspector do
[1/8] [options] 'default' [1/8] [options] 'default'
[2/8] [model] 'model' [2/8] [model] 'model'
[3/8] [policy] 'policy' [3/8] [policy] 'policy'
[4/8] [contract] 'default' [4/8] [params] 'default'
[5/8] [transaction] [5/8] [transaction]
[6/8] [step] 'in_transaction_step_1' [6/8] [step] 'in_transaction_step_1'
[7/8] [step] 'in_transaction_step_2' [7/8] [step] 'in_transaction_step_2'
@ -68,7 +68,7 @@ RSpec.describe Service::StepsInspector do
[1/8] [options] 'default' [1/8] [options] 'default'
[2/8] [model] 'model' [2/8] [model] 'model'
[3/8] [policy] 'policy' [3/8] [policy] 'policy'
[4/8] [contract] 'default' [4/8] [params] 'default'
[5/8] [transaction] [5/8] [transaction]
[6/8] [step] 'in_transaction_step_1' [6/8] [step] 'in_transaction_step_1'
[7/8] [step] 'in_transaction_step_2' [7/8] [step] 'in_transaction_step_2'
@ -91,7 +91,7 @@ RSpec.describe Service::StepsInspector do
[1/8] [options] 'default' [1/8] [options] 'default'
[2/8] [model] 'model' [2/8] [model] 'model'
[3/8] [policy] 'policy' [3/8] [policy] 'policy'
[4/8] [contract] 'default' [4/8] [params] 'default'
[5/8] [transaction] [5/8] [transaction]
[6/8] [step] 'in_transaction_step_1' [6/8] [step] 'in_transaction_step_1'
[7/8] [step] 'in_transaction_step_2' [7/8] [step] 'in_transaction_step_2'
@ -100,7 +100,7 @@ RSpec.describe Service::StepsInspector do
end end
end end
context "when the contract step is failing" do context "when the params step is failing" do
let(:parameter) { nil } let(:parameter) { nil }
it "shows the failing step" do it "shows the failing step" do
@ -108,7 +108,7 @@ RSpec.describe Service::StepsInspector do
[1/8] [options] 'default' [1/8] [options] 'default'
[2/8] [model] 'model' [2/8] [model] 'model'
[3/8] [policy] 'policy' [3/8] [policy] 'policy'
[4/8] [contract] 'default' [4/8] [params] 'default'
[5/8] [transaction] [5/8] [transaction]
[6/8] [step] 'in_transaction_step_1' [6/8] [step] 'in_transaction_step_1'
[7/8] [step] 'in_transaction_step_2' [7/8] [step] 'in_transaction_step_2'
@ -131,7 +131,7 @@ RSpec.describe Service::StepsInspector do
[1/8] [options] 'default' [1/8] [options] 'default'
[2/8] [model] 'model' [2/8] [model] 'model'
[3/8] [policy] 'policy' [3/8] [policy] 'policy'
[4/8] [contract] 'default' [4/8] [params] 'default'
[5/8] [transaction] [5/8] [transaction]
[6/8] [step] 'in_transaction_step_1' [6/8] [step] 'in_transaction_step_1'
[7/8] [step] 'in_transaction_step_2' [7/8] [step] 'in_transaction_step_2'
@ -149,7 +149,7 @@ RSpec.describe Service::StepsInspector do
[1/8] [options] 'default' [1/8] [options] 'default'
[2/8] [model] 'model' [2/8] [model] 'model'
[3/8] [policy] 'policy' <= expected to return false but got true instead [3/8] [policy] 'policy' <= expected to return false but got true instead
[4/8] [contract] 'default' [4/8] [params] 'default'
[5/8] [transaction] [5/8] [transaction]
[6/8] [step] 'in_transaction_step_1' [6/8] [step] 'in_transaction_step_1'
[7/8] [step] 'in_transaction_step_2' [7/8] [step] 'in_transaction_step_2'
@ -173,7 +173,7 @@ RSpec.describe Service::StepsInspector do
[1/8] [options] 'default' [1/8] [options] 'default'
[2/8] [model] 'model' [2/8] [model] 'model'
[3/8] [policy] 'policy' <= expected to return true but got false instead [3/8] [policy] 'policy' <= expected to return true but got false instead
[4/8] [contract] 'default' [4/8] [params] 'default'
[5/8] [transaction] [5/8] [transaction]
[6/8] [step] 'in_transaction_step_1' [6/8] [step] 'in_transaction_step_1'
[7/8] [step] 'in_transaction_step_2' [7/8] [step] 'in_transaction_step_2'
@ -223,7 +223,7 @@ RSpec.describe Service::StepsInspector do
end end
end end
context "when the contract step is failing" do context "when the params step is failing" do
let(:parameter) { nil } let(:parameter) { nil }
it "returns an error related to the contract" do it "returns an error related to the contract" do

View File

@ -2,13 +2,13 @@
RSpec.describe User::Action::TriggerPostAction do RSpec.describe User::Action::TriggerPostAction do
describe ".call" do describe ".call" do
subject(:action) { described_class.call(guardian:, post:, contract:) } subject(:action) { described_class.call(guardian:, post:, params:) }
fab!(:post) fab!(:post)
fab!(:admin) fab!(:admin)
let(:guardian) { admin.guardian } let(:guardian) { admin.guardian }
let(:contract) { User::Suspend::Contract.new(post_action:, post_edit:) } let(:params) { User::Suspend::Contract.new(post_action:, post_edit:) }
let(:post_action) { nil } let(:post_action) { nil }
let(:post_edit) { nil } let(:post_edit) { nil }

View File

@ -79,7 +79,7 @@ RSpec.describe User::Silence do
expect(User::Action::TriggerPostAction).to have_received(:call).with( expect(User::Action::TriggerPostAction).to have_received(:call).with(
guardian:, guardian:,
post: nil, post: nil,
contract: result[:contract], params: result[:params],
) )
end end
end end

View File

@ -73,7 +73,7 @@ RSpec.describe User::Suspend do
expect(User::Action::TriggerPostAction).to have_received(:call).with( expect(User::Action::TriggerPostAction).to have_received(:call).with(
guardian:, guardian:,
post: nil, post: nil,
contract: result[:contract], params: result[:params],
) )
end end