diff --git a/.github/workflows/plugin-linting.yml b/.github/workflows/plugin-linting.yml index 61610901b..1fa91eae6 100644 --- a/.github/workflows/plugin-linting.yml +++ b/.github/workflows/plugin-linting.yml @@ -55,3 +55,7 @@ jobs: - name: Rubocop if: ${{ always() }} run: bundle exec rubocop . + + - name: Syntax Tree + if: ${{ always() }} + run: bundle exec stree check --print-width=100 --plugins=plugin/trailing_comma **/*.rb Gemfile **/*.rake diff --git a/Gemfile b/Gemfile index 7da32ec03..3142de0a7 100644 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,8 @@ # frozen_string_literal: true -source 'https://rubygems.org' +source "https://rubygems.org" group :development do - gem 'rubocop-discourse' + gem "rubocop-discourse", git: "https://github.com/discourse/rubocop-discourse/", branch: "stree" + gem "syntax_tree" end diff --git a/Gemfile.lock b/Gemfile.lock index 9c730491b..4a75a02c1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,14 +1,26 @@ +GIT + remote: https://github.com/discourse/rubocop-discourse/ + revision: 8afca6460a423a11a2e0bf1f7051b18dd9a7231b + branch: stree + specs: + rubocop-discourse (2.5.0) + rubocop (>= 1.1.0) + rubocop-rspec (>= 2.0.0) + GEM remote: https://rubygems.org/ specs: ast (2.4.2) + json (2.6.2) parallel (1.22.1) parser (3.1.2.0) ast (~> 2.4.1) + prettier_print (0.1.0) rainbow (3.1.1) regexp_parser (2.5.0) rexml (3.2.5) - rubocop (1.30.1) + rubocop (1.31.2) + json (~> 2.3) parallel (~> 1.10) parser (>= 3.1.0.0) rainbow (>= 2.2.2, < 4.0) @@ -17,15 +29,14 @@ GEM rubocop-ast (>= 1.18.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) - rubocop-ast (1.18.0) + rubocop-ast (1.19.1) parser (>= 3.1.1.0) - rubocop-discourse (2.5.0) - rubocop (>= 1.1.0) - rubocop-rspec (>= 2.0.0) - rubocop-rspec (2.11.1) - rubocop (~> 1.19) + rubocop-rspec (2.12.1) + rubocop (~> 1.31) ruby-progressbar (1.11.0) - unicode-display_width (2.1.0) + syntax_tree (3.2.0) + prettier_print + unicode-display_width (2.2.0) PLATFORMS arm64-darwin-20 @@ -36,7 +47,8 @@ PLATFORMS x86_64-linux DEPENDENCIES - rubocop-discourse + rubocop-discourse! + syntax_tree BUNDLED WITH 2.3.14 diff --git a/app/controllers/admin/admin_incoming_chat_webhooks_controller.rb b/app/controllers/admin/admin_incoming_chat_webhooks_controller.rb index d590dd0a6..a8eb1e465 100644 --- a/app/controllers/admin/admin_incoming_chat_webhooks_controller.rb +++ b/app/controllers/admin/admin_incoming_chat_webhooks_controller.rb @@ -4,19 +4,21 @@ class DiscourseChat::AdminIncomingChatWebhooksController < Admin::AdminControlle requires_plugin DiscourseChat::PLUGIN_NAME def index - render_serialized({ - chat_channels: ChatChannel.public_channels, - incoming_chat_webhooks: IncomingChatWebhook.includes(:chat_channel).all - }, AdminChatIndexSerializer, root: false) + render_serialized( + { + chat_channels: ChatChannel.public_channels, + incoming_chat_webhooks: IncomingChatWebhook.includes(:chat_channel).all, + }, + AdminChatIndexSerializer, + root: false, + ) end def create - params.require([:name, :chat_channel_id]) + params.require(%i[name chat_channel_id]) chat_channel = ChatChannel.find_by(id: params[:chat_channel_id]) - if chat_channel.nil? || chat_channel.direct_message_channel? - raise Discourse::NotFound - end + raise Discourse::NotFound if chat_channel.nil? || chat_channel.direct_message_channel? webhook = IncomingChatWebhook.new(name: params[:name], chat_channel: chat_channel) if webhook.save @@ -27,23 +29,21 @@ def create end def update - params.require([:incoming_chat_webhook_id, :name, :chat_channel_id]) + params.require(%i[incoming_chat_webhook_id name chat_channel_id]) webhook = IncomingChatWebhook.find_by(id: params[:incoming_chat_webhook_id]) raise Discourse::NotFound unless webhook chat_channel = ChatChannel.find_by(id: params[:chat_channel_id]) - if chat_channel.nil? || chat_channel.direct_message_channel? - raise Discourse::NotFound - end + raise Discourse::NotFound if chat_channel.nil? || chat_channel.direct_message_channel? if webhook.update( - name: params[:name], - description: params[:description], - emoji: params[:emoji], - username: params[:username], - chat_channel: chat_channel - ) + name: params[:name], + description: params[:description], + emoji: params[:emoji], + username: params[:username], + chat_channel: chat_channel, + ) render json: success_json else render_json_error(webhook) diff --git a/app/controllers/api/category_chatables_controller.rb b/app/controllers/api/category_chatables_controller.rb index 4c74207fe..4e406fb16 100644 --- a/app/controllers/api/category_chatables_controller.rb +++ b/app/controllers/api/category_chatables_controller.rb @@ -5,17 +5,22 @@ def permissions category = Category.find(params[:id]) if category.read_restricted? - permissions = Group - .joins(:category_groups) - .where(category_groups: { category_id: category.id }) - .joins('LEFT OUTER JOIN group_users ON groups.id = group_users.group_id') - .group('groups.id', 'groups.name') - .pluck('groups.name', 'COUNT(group_users.user_id)') + permissions = + Group + .joins(:category_groups) + .where(category_groups: { category_id: category.id }) + .joins("LEFT OUTER JOIN group_users ON groups.id = group_users.group_id") + .group("groups.id", "groups.name") + .pluck("groups.name", "COUNT(group_users.user_id)") - group_names = permissions.map { |p| "@#{p[0]}" } - members_count = permissions.sum { |p| p[1].to_i } + group_names = permissions.map { |p| "@#{p[0]}" } + members_count = permissions.sum { |p| p[1].to_i } - permissions_result = { allowed_groups: group_names, members_count: members_count, private: true } + permissions_result = { + allowed_groups: group_names, + members_count: members_count, + private: true, + } else everyone_group = Group.find(Group::AUTO_GROUPS[:everyone]) diff --git a/app/controllers/api/chat_channel_memberships_controller.rb b/app/controllers/api/chat_channel_memberships_controller.rb index df4a9bf2f..c682e22f5 100644 --- a/app/controllers/api/chat_channel_memberships_controller.rb +++ b/app/controllers/api/chat_channel_memberships_controller.rb @@ -7,12 +7,13 @@ def index offset = (params[:offset] || 0).to_i limit = (params[:limit] || 50).to_i.clamp(1, 50) - memberships = ChatChannelMembershipsQuery.call( - channel, - offset: offset, - limit: limit, - username: params[:username] - ) + memberships = + ChatChannelMembershipsQuery.call( + channel, + offset: offset, + limit: limit, + username: params[:username], + ) render_serialized(memberships, UserChatChannelMembershipSerializer, root: false) end diff --git a/app/controllers/api/chat_channel_notifications_settings_controller.rb b/app/controllers/api/chat_channel_notifications_settings_controller.rb index 95c0fb5cd..fc8bd22b6 100644 --- a/app/controllers/api/chat_channel_notifications_settings_controller.rb +++ b/app/controllers/api/chat_channel_notifications_settings_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -MEMBERSHIP_EDITABLE_PARAMS = [:muted, :desktop_notification_level, :mobile_notification_level] +MEMBERSHIP_EDITABLE_PARAMS = %i[muted desktop_notification_level mobile_notification_level] class DiscourseChat::Api::ChatChannelNotificationsSettingsController < DiscourseChat::Api::ChatChannelsController def update diff --git a/app/controllers/api/chat_channels_controller.rb b/app/controllers/api/chat_channels_controller.rb index 565d29e51..538bd5566 100644 --- a/app/controllers/api/chat_channels_controller.rb +++ b/app/controllers/api/chat_channels_controller.rb @@ -5,15 +5,16 @@ class DiscourseChat::Api::ChatChannelsController < DiscourseChat::Api def index - options = { - status: params[:status] ? ChatChannel.statuses[params[:status]] : nil - }.merge(params.permit(:filter, :limit, :offset)).symbolize_keys! - - channels = DiscourseChat::ChatChannelFetcher.secured_public_channels( - guardian, - UserChatChannelMembership.where(user: current_user), - options - ) + options = { status: params[:status] ? ChatChannel.statuses[params[:status]] : nil }.merge( + params.permit(:filter, :limit, :offset), + ).symbolize_keys! + + channels = + DiscourseChat::ChatChannelFetcher.secured_public_channels( + guardian, + UserChatChannelMembership.where(user: current_user), + options, + ) render_serialized(channels, ChatChannelSerializer) end @@ -24,13 +25,13 @@ def update chat_channel = find_chat_channel if chat_channel.direct_message_channel? - raise Discourse::InvalidParameters.new(I18n.t("chat.errors.cant_update_direct_message_channel")) + raise Discourse::InvalidParameters.new( + I18n.t("chat.errors.cant_update_direct_message_channel"), + ) end params_to_edit = editable_params(params, chat_channel) - params_to_edit.each do |k, v| - params_to_edit[k] = nil if params_to_edit[k].blank? - end + params_to_edit.each { |k, v| params_to_edit[k] = nil if params_to_edit[k].blank? } if ActiveRecord::Type::Boolean.new.deserialize(params_to_edit[:auto_join_users]) auto_join_limiter(chat_channel).performed! @@ -56,23 +57,29 @@ def find_chat_channel end def find_membership - membership = UserChatChannelMembership - .includes(:user, :chat_channel) - .find_by!(user: current_user, chat_channel_id: params.require(:chat_channel_id)) + membership = + UserChatChannelMembership.includes(:user, :chat_channel).find_by!( + user: current_user, + chat_channel_id: params.require(:chat_channel_id), + ) guardian.ensure_can_see_chat_channel!(membership.chat_channel) membership end def auto_join_limiter(chat_channel) - RateLimiter.new(current_user, "auto_join_users_channel_#{chat_channel.id}", 1, 3.minutes, apply_limit_to_staff: true) + RateLimiter.new( + current_user, + "auto_join_users_channel_#{chat_channel.id}", + 1, + 3.minutes, + apply_limit_to_staff: true, + ) end def editable_params(params, chat_channel) permitted_params = CHAT_CHANNEL_EDITABLE_PARAMS - if chat_channel.category_channel? - permitted_params += CATEGORY_CHAT_CHANNEL_EDITABLE_PARAMS - end + permitted_params += CATEGORY_CHAT_CHANNEL_EDITABLE_PARAMS if chat_channel.category_channel? params.permit(*permitted_params) end diff --git a/app/controllers/chat_base_controller.rb b/app/controllers/chat_base_controller.rb index e6fe0b40f..58cc7fd8e 100644 --- a/app/controllers/chat_base_controller.rb +++ b/app/controllers/chat_base_controller.rb @@ -12,13 +12,9 @@ def ensure_can_chat end def set_channel_and_chatable_with_access_check(chat_channel_id: nil) - if chat_channel_id.blank? - params.require(:chat_channel_id) - end + params.require(:chat_channel_id) if chat_channel_id.blank? id_or_name = chat_channel_id || params[:chat_channel_id] - @chat_channel = DiscourseChat::ChatChannelFetcher.find_with_access_check( - id_or_name, guardian - ) + @chat_channel = DiscourseChat::ChatChannelFetcher.find_with_access_check(id_or_name, guardian) @chatable = @chat_channel.chatable end end diff --git a/app/controllers/chat_channels_controller.rb b/app/controllers/chat_channels_controller.rb index d5341dcec..898e661b8 100644 --- a/app/controllers/chat_channels_controller.rb +++ b/app/controllers/chat_channels_controller.rb @@ -1,11 +1,7 @@ # frozen_string_literal: true class DiscourseChat::ChatChannelsController < DiscourseChat::ChatBaseController - before_action :set_channel_and_chatable_with_access_check, except: [ - :index, - :create, - :search - ] + before_action :set_channel_and_chatable_with_access_check, except: %i[index create search] def index structured = DiscourseChat::ChatChannelFetcher.structured(guardian) @@ -29,15 +25,14 @@ def unfollow end def create - params.require([:id, :name]) + params.require(%i[id name]) guardian.ensure_can_create_chat_channel! - raise Discourse::InvalidParameters.new(:name) if params[:name].length > SiteSetting.max_topic_title_length + if params[:name].length > SiteSetting.max_topic_title_length + raise Discourse::InvalidParameters.new(:name) + end - exists = ChatChannel.exists?( - chatable_type: 'Category', - chatable_id: params[:id], - name: params[:name] - ) + exists = + ChatChannel.exists?(chatable_type: "Category", chatable_id: params[:id], name: params[:name]) if exists raise Discourse::InvalidParameters.new(I18n.t("chat.errors.channel_exists_for_category")) end @@ -47,13 +42,14 @@ def create auto_join_users = ActiveRecord::Type::Boolean.new.deserialize(params[:auto_join_users]) || false - chat_channel = ChatChannel.create!( - chatable: chatable, - name: params[:name], - description: params[:description], - user_count: 1, - auto_join_users: auto_join_users - ) + chat_channel = + ChatChannel.create!( + chatable: chatable, + name: params[:name], + description: params[:description], + user_count: 1, + auto_join_users: auto_join_users, + ) chat_channel.user_chat_channel_memberships.create!(user: current_user, following: true) if chat_channel.auto_join_users @@ -84,13 +80,14 @@ def search params.require(:filter) filter = params[:filter]&.downcase memberships = UserChatChannelMembership.where(user: current_user) - public_channels = DiscourseChat::ChatChannelFetcher.secured_public_channels( - guardian, - memberships, - following: false, - filter: filter, - status: :open - ) + public_channels = + DiscourseChat::ChatChannelFetcher.secured_public_channels( + guardian, + memberships, + following: false, + filter: filter, + status: :open, + ) users = User.joins(:user_option).where.not(id: current_user.id) if !DiscourseChat.allowed_group_ids.include?(Group::AUTO_GROUPS[:everyone]) @@ -102,24 +99,37 @@ def search if SiteSetting.prioritize_username_in_ux || !SiteSetting.enable_names users = users.where("users.username_lower ILIKE ?", like_filter) else - users = users.where("LOWER(users.name) ILIKE ? OR users.username_lower ILIKE ?", like_filter, like_filter) + users = + users.where( + "LOWER(users.name) ILIKE ? OR users.username_lower ILIKE ?", + like_filter, + like_filter, + ) end users = users.limit(25).uniq - direct_message_channels = users.count > 0 ? - ChatChannel - .includes(chatable: :users) - .joins(direct_message_channel: :direct_message_users) - .group(1) - .having("ARRAY[?] <@ ARRAY_AGG(user_id) AND ARRAY[?] && ARRAY_AGG(user_id)", [current_user.id], users.map(&:id)) : [] + direct_message_channels = + ( + if users.count > 0 + ChatChannel + .includes(chatable: :users) + .joins(direct_message_channel: :direct_message_users) + .group(1) + .having( + "ARRAY[?] <@ ARRAY_AGG(user_id) AND ARRAY[?] && ARRAY_AGG(user_id)", + [current_user.id], + users.map(&:id), + ) + else + [] + end + ) user_ids_with_channel = [] direct_message_channels.each do |dm_channel| user_ids = dm_channel.chatable.users.map(&:id) - if user_ids.count < 3 - user_ids_with_channel.concat(user_ids) - end + user_ids_with_channel.concat(user_ids) if user_ids.count < 3 end users_without_channel = users.filter { |u| !user_ids_with_channel.include?(u.id) } @@ -130,11 +140,15 @@ def search users_without_channel << current_user end - render_serialized({ - public_channels: public_channels, - direct_message_channels: direct_message_channels, - users: users_without_channel - }, ChatChannelSearchSerializer, root: false) + render_serialized( + { + public_channels: public_channels, + direct_message_channels: direct_message_channels, + users: users_without_channel, + }, + ChatChannelSearchSerializer, + root: false, + ) end def archive @@ -155,8 +169,8 @@ def archive topic_id: params[:topic_id], topic_title: params[:title], category_id: params[:category_id], - tags: params[:tags] - } + tags: params[:tags], + }, ) render json: success_json @@ -179,9 +193,8 @@ def change_status # we only want to use this endpoint for open/closed status changes, # the others are more "special" and are handled by the archive endpoint - if !ChatChannel.statuses.keys.include?(params[:status]) || - params[:status] == "read_only" || - params[:status] == "archive" + if !ChatChannel.statuses.keys.include?(params[:status]) || params[:status] == "read_only" || + params[:status] == "archive" raise Discourse::InvalidParameters end @@ -207,8 +220,8 @@ def destroy "chat_channel_delete", { chat_channel_id: @chat_channel.id, - chat_channel_name: @chat_channel.title(current_user) - } + chat_channel_name: @chat_channel.title(current_user), + }, ) end rescue ActiveRecord::Rollback diff --git a/app/controllers/chat_controller.rb b/app/controllers/chat_controller.rb index 490ebc2ca..47ff39d0e 100644 --- a/app/controllers/chat_controller.rb +++ b/app/controllers/chat_controller.rb @@ -3,32 +3,27 @@ class DiscourseChat::ChatController < DiscourseChat::ChatBaseController PAST_MESSAGE_LIMIT = 20 FUTURE_MESSAGE_LIMIT = 40 - PAST = 'past' - FUTURE = 'future' + PAST = "past" + FUTURE = "future" CHAT_DIRECTIONS = [PAST, FUTURE] # Other endpoints use set_channel_and_chatable_with_access_check, but # these endpoints require a standalone find because they need to be # able to get deleted channels and recover them. - before_action :find_chatable, only: [:enable_chat, :disable_chat] - before_action :find_chat_message, only: [ - :delete, - :restore, - :lookup_message, - :edit_message, - :rebake, - :message_link - ] - before_action :set_channel_and_chatable_with_access_check, except: [ - :respond, - :enable_chat, - :disable_chat, - :message_link, - :lookup_message, - :set_user_chat_status, - :dismiss_retention_reminder, - :flag - ] + before_action :find_chatable, only: %i[enable_chat disable_chat] + before_action :find_chat_message, + only: %i[delete restore lookup_message edit_message rebake message_link] + before_action :set_channel_and_chatable_with_access_check, + except: %i[ + respond + enable_chat + disable_chat + message_link + lookup_message + set_user_chat_status + dismiss_retention_reminder + flag + ] def respond render @@ -37,9 +32,7 @@ def respond def enable_chat chat_channel = ChatChannel.with_deleted.find_by(chatable: @chatable) - if chat_channel - guardian.ensure_can_see_chat_channel!(chat_channel) - end + guardian.ensure_can_see_chat_channel!(chat_channel) if chat_channel if chat_channel && chat_channel.trashed? chat_channel.recover! @@ -57,10 +50,11 @@ def enable_chat end if success - membership = UserChatChannelMembership.find_or_initialize_by( - chat_channel: chat_channel, - user: current_user - ) + membership = + UserChatChannelMembership.find_or_initialize_by( + chat_channel: chat_channel, + user: current_user, + ) membership.following = true membership.save! render_serialized(chat_channel, ChatChannelSerializer) @@ -72,9 +66,7 @@ def enable_chat def disable_chat chat_channel = ChatChannel.with_deleted.find_by(chatable: @chatable) guardian.ensure_can_see_chat_channel!(chat_channel) - if chat_channel.trashed? - return render json: success_json - end + return render json: success_json if chat_channel.trashed? chat_channel.trash!(current_user) success = chat_channel.save @@ -93,11 +85,12 @@ def disable_chat def create_message DiscourseChat::ChatMessageRateLimiter.run!(current_user) - @user_chat_channel_membership = UserChatChannelMembership.find_by( - chat_channel: @chat_channel, - user: current_user, - following: true - ) + @user_chat_channel_membership = + UserChatChannelMembership.find_by( + chat_channel: @chat_channel, + user: current_user, + following: true, + ) raise Discourse::InvalidAccess unless @user_chat_channel_membership reply_to_msg_id = params[:in_reply_to_id] @@ -108,23 +101,20 @@ def create_message content = params[:message] - chat_message_creator = DiscourseChat::ChatMessageCreator.create( - chat_channel: @chat_channel, - user: current_user, - in_reply_to_id: reply_to_msg_id, - content: content, - staged_id: params[:staged_id], - upload_ids: params[:upload_ids] - ) + chat_message_creator = + DiscourseChat::ChatMessageCreator.create( + chat_channel: @chat_channel, + user: current_user, + in_reply_to_id: reply_to_msg_id, + content: content, + staged_id: params[:staged_id], + upload_ids: params[:upload_ids], + ) - if chat_message_creator.failed? - return render_json_error(chat_message_creator.error) - end + return render_json_error(chat_message_creator.error) if chat_message_creator.failed? @chat_channel.touch(:last_message_sent_at) - @user_chat_channel_membership.update( - last_read_message_id: chat_message_creator.chat_message.id - ) + @user_chat_channel_membership.update(last_read_message_id: chat_message_creator.chat_message.id) if @chat_channel.direct_message_channel? @chat_channel.user_chat_channel_memberships.update_all(following: true) @@ -134,35 +124,42 @@ def create_message ChatPublisher.publish_user_tracking_state( current_user, @chat_channel.id, - chat_message_creator.chat_message.id + chat_message_creator.chat_message.id, ) render json: success_json end def edit_message guardian.ensure_can_edit_chat!(@message) - chat_message_updater = DiscourseChat::ChatMessageUpdater.update( - chat_message: @message, - new_content: params[:new_message], - upload_ids: params[:upload_ids] || [] - ) + chat_message_updater = + DiscourseChat::ChatMessageUpdater.update( + chat_message: @message, + new_content: params[:new_message], + upload_ids: params[:upload_ids] || [], + ) - if chat_message_updater.failed? - return render_json_error(chat_message_updater.error) - end + return render_json_error(chat_message_updater.error) if chat_message_updater.failed? render json: success_json end def update_user_last_read - membership = UserChatChannelMembership.find_by(user: current_user, chat_channel: @chat_channel, following: true) + membership = + UserChatChannelMembership.find_by( + user: current_user, + chat_channel: @chat_channel, + following: true, + ) raise Discourse::NotFound if membership.nil? if membership.last_read_message_id && params[:message_id].to_i < membership.last_read_message_id raise Discourse::InvalidParameters.new(:message_id) end - unless ChatMessage.with_deleted.exists?(chat_channel_id: @chat_channel.id, id: params[:message_id]) + unless ChatMessage.with_deleted.exists?( + chat_channel_id: @chat_channel.id, + id: params[:message_id], + ) raise Discourse::NotFound end @@ -172,17 +169,13 @@ def update_user_last_read .where(notification_type: Notification.types[:chat_mention]) .where(user: current_user) .where(read: false) - .joins('INNER JOIN chat_mentions ON chat_mentions.notification_id = notifications.id') - .joins('INNER JOIN chat_messages ON chat_mentions.chat_message_id = chat_messages.id') - .where('chat_messages.id <= ?', params[:message_id].to_i) - .where('chat_messages.chat_channel_id = ?', @chat_channel.id) + .joins("INNER JOIN chat_mentions ON chat_mentions.notification_id = notifications.id") + .joins("INNER JOIN chat_messages ON chat_mentions.chat_message_id = chat_messages.id") + .where("chat_messages.id <= ?", params[:message_id].to_i) + .where("chat_messages.chat_channel_id = ?", @chat_channel.id) .update_all(read: true) - ChatPublisher.publish_user_tracking_state( - current_user, - @chat_channel.id, - params[:message_id] - ) + ChatPublisher.publish_user_tracking_state(current_user, @chat_channel.id, params[:message_id]) render json: success_json end @@ -191,7 +184,11 @@ def messages page_size = params[:page_size]&.to_i || 1000 direction = params[:direction].to_s message_id = params[:message_id] - if page_size > 50 || (message_id.blank? ^ direction.blank? && (direction.present? && !CHAT_DIRECTIONS.include?(direction))) + if page_size > 50 || + ( + message_id.blank? ^ direction.blank? && + (direction.present? && !CHAT_DIRECTIONS.include?(direction)) + ) raise Discourse::InvalidParameters end @@ -199,7 +196,7 @@ def messages messages = messages.with_deleted if guardian.can_moderate_chat?(@chatable) if message_id.present? - condition = direction == PAST ? '<' : '>' + condition = direction == PAST ? "<" : ">" messages = messages.where("id #{condition} ?", message_id.to_i) end @@ -221,26 +218,25 @@ def messages can_load_more_past = messages.size == page_size end - chat_view = ChatView.new( - chat_channel: @chat_channel, - chat_messages: direction == FUTURE ? messages : messages.reverse, - user: current_user, - can_load_more_past: can_load_more_past, - can_load_more_future: can_load_more_future - ) + chat_view = + ChatView.new( + chat_channel: @chat_channel, + chat_messages: direction == FUTURE ? messages : messages.reverse, + user: current_user, + can_load_more_past: can_load_more_past, + can_load_more_future: can_load_more_future, + ) render_serialized(chat_view, ChatViewSerializer, root: false) end def react - params.require([:message_id, :emoji, :react_action]) + params.require(%i[message_id emoji react_action]) guardian.ensure_can_react! - DiscourseChat::ChatMessageReactor.new( - current_user, @chat_channel - ).react!( + DiscourseChat::ChatMessageReactor.new(current_user, @chat_channel).react!( message_id: params[:message_id], react_action: params[:react_action].to_sym, - emoji: params[:emoji] + emoji: params[:emoji], ) render json: success_json @@ -280,10 +276,11 @@ def message_link return render_404 if @message.blank? || @message.deleted_at.present? return render_404 if @message.chat_channel.blank? set_channel_and_chatable_with_access_check(chat_channel_id: @message.chat_channel_id) - render json: success_json.merge( - chat_channel_id: @chat_channel.id, - chat_channel_title: @chat_channel.title(current_user) - ) + render json: + success_json.merge( + chat_channel_id: @chat_channel.id, + chat_channel_title: @chat_channel.title(current_user), + ) end def lookup_message @@ -291,26 +288,29 @@ def lookup_message messages = preloaded_chat_message_query.where(chat_channel: @chat_channel) messages = messages.with_deleted if guardian.can_moderate_chat?(@chatable) - past_messages = messages - .where("created_at < ?", @message.created_at) - .order(created_at: :desc) - .limit(PAST_MESSAGE_LIMIT) - - future_messages = messages - .where("created_at > ?", @message.created_at) - .order(created_at: :asc) - .limit(FUTURE_MESSAGE_LIMIT) + past_messages = + messages + .where("created_at < ?", @message.created_at) + .order(created_at: :desc) + .limit(PAST_MESSAGE_LIMIT) + + future_messages = + messages + .where("created_at > ?", @message.created_at) + .order(created_at: :asc) + .limit(FUTURE_MESSAGE_LIMIT) can_load_more_past = past_messages.count == PAST_MESSAGE_LIMIT can_load_more_future = future_messages.count == FUTURE_MESSAGE_LIMIT messages = [past_messages.reverse, [@message], future_messages].reduce([], :concat) - chat_view = ChatView.new( - chat_channel: @chat_channel, - chat_messages: messages, - user: current_user, - can_load_more_past: can_load_more_past, - can_load_more_future: can_load_more_future - ) + chat_view = + ChatView.new( + chat_channel: @chat_channel, + chat_messages: messages, + user: current_user, + can_load_more_past: can_load_more_past, + can_load_more_future: can_load_more_future, + ) render_serialized(chat_view, ChatViewSerializer, root: false) end @@ -324,28 +324,27 @@ def set_user_chat_status def invite_users params.require(:user_ids) - users = User - .includes(:groups) - .joins(:user_option) - .where(user_options: { chat_enabled: true }) - .not_suspended - .where(id: params[:user_ids]) + users = + User + .includes(:groups) + .joins(:user_option) + .where(user_options: { chat_enabled: true }) + .not_suspended + .where(id: params[:user_ids]) users.each do |user| guardian = Guardian.new(user) if guardian.can_chat?(user) && guardian.can_see_chat_channel?(@chat_channel) data = { - message: 'chat.invitation_notification', + message: "chat.invitation_notification", chat_channel_id: @chat_channel.id, chat_channel_title: @chat_channel.title(user), invited_by_username: current_user.username, } - if params[:chat_message_id] - data[:chat_message_id] = params[:chat_message_id] - end + data[:chat_message_id] = params[:chat_message_id] if params[:chat_message_id] user.notifications.create( notification_type: Notification.types[:chat_invitation], high_priority: true, - data: data.to_json + data: data.to_json, ) end end @@ -356,11 +355,18 @@ def invite_users def dismiss_retention_reminder params.require(:chatable_type) guardian.ensure_can_chat!(current_user) - raise Discourse::InvalidParameters unless ChatChannel.chatable_types.include?(params[:chatable_type]) + unless ChatChannel.chatable_types.include?(params[:chatable_type]) + raise Discourse::InvalidParameters + end - field = ChatChannel.public_channel_chatable_types.include?(params[:chatable_type]) ? - :dismissed_channel_retention_reminder : - :dismissed_dm_retention_reminder + field = + ( + if ChatChannel.public_channel_chatable_types.include?(params[:chatable_type]) + :dismissed_channel_retention_reminder + else + :dismissed_dm_retention_reminder + end + ) current_user.user_option.update(field => true) render json: success_json end @@ -369,9 +375,12 @@ def quote_messages params.require(:message_ids) message_ids = params[:message_ids].map(&:to_i) - markdown = ChatTranscriptService.new( - @chat_channel, current_user, messages_or_ids: message_ids - ).generate_markdown + markdown = + ChatTranscriptService.new( + @chat_channel, + current_user, + messages_or_ids: message_ids, + ).generate_markdown render json: success_json.merge(markdown: markdown) end @@ -380,29 +389,36 @@ def move_messages_to_channel params.require(:destination_channel_id) raise Discourse::InvalidAccess if !guardian.can_move_chat_messages?(@chat_channel) - destination_channel = DiscourseChat::ChatChannelFetcher.find_with_access_check(params[:destination_channel_id], guardian) + destination_channel = + DiscourseChat::ChatChannelFetcher.find_with_access_check( + params[:destination_channel_id], + guardian, + ) begin message_ids = params[:message_ids].map(&:to_i) - moved_messages = DiscourseChat::MessageMover.new( - acting_user: current_user, source_channel: @chat_channel, message_ids: message_ids - ).move_to_channel(destination_channel) - rescue DiscourseChat::MessageMover::NoMessagesFound, DiscourseChat::MessageMover::InvalidChannel => err + moved_messages = + DiscourseChat::MessageMover.new( + acting_user: current_user, + source_channel: @chat_channel, + message_ids: message_ids, + ).move_to_channel(destination_channel) + rescue DiscourseChat::MessageMover::NoMessagesFound, + DiscourseChat::MessageMover::InvalidChannel => err return render_json_error(err.message) end - render json: success_json.merge( - destination_channel_id: destination_channel.id, - destination_channel_title: destination_channel.title(current_user), - first_moved_message_id: moved_messages.first.id - ) + render json: + success_json.merge( + destination_channel_id: destination_channel.id, + destination_channel_title: destination_channel.title(current_user), + first_moved_message_id: moved_messages.first.id, + ) end def flag params.require([:chat_message_id]) - chat_message = ChatMessage - .includes(:chat_channel) - .find_by(id: params[:chat_message_id]) + chat_message = ChatMessage.includes(:chat_channel).find_by(id: params[:chat_message_id]) raise Discourse::InvalidParameters unless chat_message set_channel_and_chatable_with_access_check(chat_channel_id: chat_message.chat_channel_id) @@ -419,13 +435,11 @@ def flag def set_draft if params[:data].present? - ChatDraft - .find_or_initialize_by(user: current_user, chat_channel_id: @chat_channel.id) - .update(data: params[:data]) + ChatDraft.find_or_initialize_by(user: current_user, chat_channel_id: @chat_channel.id).update( + data: params[:data], + ) else - ChatDraft - .where(user: current_user, chat_channel_id: @chat_channel.id) - .destroy_all + ChatDraft.where(user: current_user, chat_channel_id: @chat_channel.id).destroy_all end render json: success_json @@ -451,10 +465,8 @@ def find_chatable end def find_chat_message - @message = ChatMessage - .unscoped - .includes(chat_channel: :chatable) - .find_by(id: params[:message_id]) + @message = + ChatMessage.unscoped.includes(chat_channel: :chatable).find_by(id: params[:message_id]) raise Discourse::NotFound unless @message end diff --git a/app/controllers/direct_messages_controller.rb b/app/controllers/direct_messages_controller.rb index f01067d42..788b0e826 100644 --- a/app/controllers/direct_messages_controller.rb +++ b/app/controllers/direct_messages_controller.rb @@ -16,10 +16,11 @@ def index direct_message_channel = DirectMessageChannel.for_user_ids(users.map(&:id).uniq) if direct_message_channel - chat_channel = ChatChannel.find_by( - chatable_id: direct_message_channel.id, - chatable_type: 'DirectMessageChannel' - ) + chat_channel = + ChatChannel.find_by( + chatable_id: direct_message_channel.id, + chatable_type: "DirectMessageChannel", + ) render_serialized(chat_channel, ChatChannelSerializer, root: "chat_channel") else render body: nil, status: 404 @@ -31,17 +32,12 @@ def index def users_from_usernames(current_user, params) params.require(:usernames) - usernames = if params[:usernames].is_a?(String) - params[:usernames].split(",") - else - params[:usernames] - end + usernames = + (params[:usernames].is_a?(String) ? params[:usernames].split(",") : params[:usernames]) users = [current_user] other_usernames = usernames - [current_user.username] - if other_usernames.any? - users.concat(User.where(username: other_usernames).to_a) - end + users.concat(User.where(username: other_usernames).to_a) if other_usernames.any? users end end diff --git a/app/controllers/incoming_chat_webhooks_controller.rb b/app/controllers/incoming_chat_webhooks_controller.rb index 5818dfe39..4345a7407 100644 --- a/app/controllers/incoming_chat_webhooks_controller.rb +++ b/app/controllers/incoming_chat_webhooks_controller.rb @@ -11,9 +11,7 @@ class DiscourseChat::IncomingChatWebhooksController < ApplicationController def create_message debug_payload - hijack do - process_webhook_payload(text: params[:text], key: params[:key]) - end + hijack { process_webhook_payload(text: params[:text], key: params[:key]) } end # See https://api.slack.com/reference/messaging/payload for the @@ -24,16 +22,17 @@ def create_message_slack_compatible debug_payload # See note in validate_payload on why this is needed - attachments = if params[:payload].present? - payload = params[:payload] - if String === payload - payload = JSON.parse(payload) - payload.deep_symbolize_keys! + attachments = + if params[:payload].present? + payload = params[:payload] + if String === payload + payload = JSON.parse(payload) + payload.deep_symbolize_keys! + end + payload[:attachments] + else + params[:attachments] end - payload[:attachments] - else - params[:attachments] - end if params[:text].present? text = DiscourseChat::SlackCompatibility.process_text(params[:text]) @@ -41,9 +40,7 @@ def create_message_slack_compatible text = DiscourseChat::SlackCompatibility.process_legacy_attachments(attachments) end - hijack do - process_webhook_payload(text: text, key: params[:key]) - end + hijack { process_webhook_payload(text: text, key: params[:key]) } rescue JSON::ParserError raise Discourse::InvalidParameters end @@ -54,12 +51,13 @@ def process_webhook_payload(text:, key:) validate_message_length(text) webhook = find_and_rate_limit_webhook(key) - chat_message_creator = DiscourseChat::ChatMessageCreator.create( - chat_channel: webhook.chat_channel, - user: Discourse.system_user, - content: text, - incoming_chat_webhook: webhook - ) + chat_message_creator = + DiscourseChat::ChatMessageCreator.create( + chat_channel: webhook.chat_channel, + user: Discourse.system_user, + content: text, + incoming_chat_webhook: webhook, + ) if chat_message_creator.failed? render_json_error(chat_message_creator.error) else @@ -72,13 +70,20 @@ def find_and_rate_limit_webhook(key) raise Discourse::NotFound unless webhook # Rate limit to 10 messages per-minute. We can move to a site setting in the future if needed. - RateLimiter.new(nil, "incoming_chat_webhook_#{webhook.id}", WEBHOOK_MESSAGES_PER_MINUTE_LIMIT, 1.minute).performed! + RateLimiter.new( + nil, + "incoming_chat_webhook_#{webhook.id}", + WEBHOOK_MESSAGES_PER_MINUTE_LIMIT, + 1.minute, + ).performed! webhook end def validate_message_length(message) return if message.length <= WEBHOOK_MAX_MESSAGE_LENGTH - raise Discourse::InvalidParameters.new("Body cannot be over #{WEBHOOK_MAX_MESSAGE_LENGTH} characters") + raise Discourse::InvalidParameters.new( + "Body cannot be over #{WEBHOOK_MAX_MESSAGE_LENGTH} characters", + ) end def validate_payload @@ -95,8 +100,10 @@ def validate_payload def debug_payload return if !SiteSetting.chat_debug_webhook_payloads Rails.logger.warn( - "Debugging chat webhook payload: " + \ - JSON.dump({ payload: params[:payload], attachments: params[:attachments], text: params[:text] }) + "Debugging chat webhook payload: " + + JSON.dump( + { payload: params[:payload], attachments: params[:attachments], text: params[:text] }, + ), ) end end diff --git a/app/jobs/regular/auto_join_channel_batch.rb b/app/jobs/regular/auto_join_channel_batch.rb index 8d2b324db..e07adb81e 100644 --- a/app/jobs/regular/auto_join_channel_batch.rb +++ b/app/jobs/regular/auto_join_channel_batch.rb @@ -6,11 +6,12 @@ def execute(args) return "starts_at or ends_at missing" if args[:starts_at].blank? || args[:ends_at].blank? return "End is higher than start" if args[:ends_at] < args[:starts_at] - channel = ChatChannel.find_by( - id: args[:chat_channel_id], - auto_join_users: true, - chatable_type: 'Category' - ) + channel = + ChatChannel.find_by( + id: args[:chat_channel_id], + auto_join_users: true, + chatable_type: "Category", + ) return if !channel @@ -24,7 +25,7 @@ def execute(args) suspended_until: Time.zone.now, last_seen_at: 3.months.ago, channel_category: channel.chatable_id, - mode: UserChatChannelMembership.join_modes[:automatic] + mode: UserChatChannelMembership.join_modes[:automatic], } new_member_ids = DB.query_single(create_memberships_query(category), query_args) @@ -35,10 +36,7 @@ def execute(args) WHERE id = :channel_id SQL - ChatPublisher.publish_new_channel( - channel.reload, - User.where(id: new_member_ids) - ) + ChatPublisher.publish_new_channel(channel.reload, User.where(id: new_member_ids)) end private @@ -53,12 +51,10 @@ def create_memberships_query(category) uccm.chat_channel_id = :chat_channel_id AND uccm.user_id = users.id SQL - if category.read_restricted? - query += <<~SQL + query += <<~SQL if category.read_restricted? INNER JOIN group_users gu ON gu.user_id = users.id LEFT OUTER JOIN category_groups cg ON cg.group_id = gu.group_id SQL - end query += <<~SQL WHERE (users.id >= :start AND users.id <= :end) AND @@ -70,13 +66,11 @@ def create_memberships_query(category) uccm.id IS NULL SQL - if category.read_restricted? - query += <<~SQL + query += <<~SQL if category.read_restricted? AND cg.category_id = :channel_category SQL - end - query += 'RETURNING user_chat_channel_memberships.user_id' + query += "RETURNING user_chat_channel_memberships.user_id" end end end diff --git a/app/jobs/regular/auto_manage_channel_memberships.rb b/app/jobs/regular/auto_manage_channel_memberships.rb index c8f08647c..8675d676d 100644 --- a/app/jobs/regular/auto_manage_channel_memberships.rb +++ b/app/jobs/regular/auto_manage_channel_memberships.rb @@ -3,11 +3,12 @@ module Jobs class AutoManageChannelMemberships < ::Jobs::Base def execute(args) - channel = ChatChannel.includes(:chatable).find_by( - id: args[:chat_channel_id], - auto_join_users: true, - chatable_type: 'Category' - ) + channel = + ChatChannel.includes(:chatable).find_by( + id: args[:chat_channel_id], + auto_join_users: true, + chatable_type: "Category", + ) return if !channel @@ -21,7 +22,9 @@ def execute(args) Jobs.enqueue( :auto_join_channel_batch, - chat_channel_id: channel.id, starts_at: starts_at, ends_at: ends_at + chat_channel_id: channel.id, + starts_at: starts_at, + ends_at: ends_at, ) processed += batch.size @@ -33,30 +36,30 @@ def execute(args) def auto_join_query(channel) category = channel.chatable - users = User - .real - .activated - .not_suspended - .not_staged - .distinct - .select(:id, 'users.id AS query_user_id') - .where('last_seen_at IS NULL OR last_seen_at > ?', 3.months.ago) - .joins(:user_option) - .where(user_options: { chat_enabled: true }) - .joins( - <<~SQL + users = + User + .real + .activated + .not_suspended + .not_staged + .distinct + .select(:id, "users.id AS query_user_id") + .where("last_seen_at IS NULL OR last_seen_at > ?", 3.months.ago) + .joins(:user_option) + .where(user_options: { chat_enabled: true }) + .joins(<<~SQL) LEFT OUTER JOIN user_chat_channel_memberships uccm ON uccm.chat_channel_id = #{channel.id} AND uccm.user_id = users.id SQL - ) - .where('uccm.id IS NULL') + .where("uccm.id IS NULL") if category.read_restricted? - users = users - .joins(:group_users) - .joins('INNER JOIN category_groups cg ON cg.group_id = group_users.group_id') - .where('cg.category_id = ?', channel.chatable_id) + users = + users + .joins(:group_users) + .joins("INNER JOIN category_groups cg ON cg.group_id = group_users.group_id") + .where("cg.category_id = ?", channel.chatable_id) end users diff --git a/app/jobs/regular/chat_channel_archive.rb b/app/jobs/regular/chat_channel_archive.rb index b6b1e7868..4be04ec8a 100644 --- a/app/jobs/regular/chat_channel_archive.rb +++ b/app/jobs/regular/chat_channel_archive.rb @@ -9,7 +9,9 @@ def execute(args = {}) # this should not really happen, but better to do this than throw an error if channel_archive.blank? - Rails.logger.warn("Chat channel archive #{args[:chat_channel_archive_id]} could not be found, aborting archive job.") + Rails.logger.warn( + "Chat channel archive #{args[:chat_channel_archive_id]} could not be found, aborting archive job.", + ) return end @@ -17,10 +19,8 @@ def execute(args = {}) DistributedMutex.synchronize( "archive_chat_channel_#{channel_archive.chat_channel_id}", - validity: 20.minutes - ) do - DiscourseChat::ChatChannelArchiveService.new(channel_archive).execute - end + validity: 20.minutes, + ) { DiscourseChat::ChatChannelArchiveService.new(channel_archive).execute } end end end diff --git a/app/jobs/regular/chat_channel_delete.rb b/app/jobs/regular/chat_channel_delete.rb index 815995549..ac89be4db 100644 --- a/app/jobs/regular/chat_channel_delete.rb +++ b/app/jobs/regular/chat_channel_delete.rb @@ -7,7 +7,9 @@ def execute(args = {}) # this should not really happen, but better to do this than throw an error if chat_channel.blank? - Rails.logger.warn("Chat channel #{args[:chat_channel_id]} could not be found, aborting delete job.") + Rails.logger.warn( + "Chat channel #{args[:chat_channel_id]} could not be found, aborting delete job.", + ) return end @@ -23,7 +25,9 @@ def execute(args = {}) ChatDraft.where(chat_channel: chat_channel).delete_all UserChatChannelMembership.where(chat_channel: chat_channel).delete_all - Rails.logger.debug("Deleting chat messages, mentions, revisions, and uploads for channel #{chat_channel.id}") + Rails.logger.debug( + "Deleting chat messages, mentions, revisions, and uploads for channel #{chat_channel.id}", + ) ChatMessage.transaction do chat_messages = ChatMessage.where(chat_channel: chat_channel) message_ids = chat_messages.select(:id) @@ -38,7 +42,8 @@ def execute(args = {}) # only the messages and the channel are Trashable, everything else gets # permanently destroyed chat_messages.update_all( - deleted_by_id: chat_channel.deleted_by_id, deleted_at: Time.zone.now + deleted_by_id: chat_channel.deleted_by_id, + deleted_at: Time.zone.now, ) end end diff --git a/app/jobs/regular/chat_notify_mentioned.rb b/app/jobs/regular/chat_notify_mentioned.rb index 0e1337d70..85699fb66 100644 --- a/app/jobs/regular/chat_notify_mentioned.rb +++ b/app/jobs/regular/chat_notify_mentioned.rb @@ -2,10 +2,15 @@ module Jobs class ChatNotifyMentioned < ::Jobs::Base - def execute(args = {}) - @chat_message = ChatMessage.includes(:user, :revisions, chat_channel: :chatable).find_by(id: args[:chat_message_id]) - return if @chat_message.nil? || @chat_message.revisions.where("created_at > ?", args[:timestamp]).any? + @chat_message = + ChatMessage.includes(:user, :revisions, chat_channel: :chatable).find_by( + id: args[:chat_message_id], + ) + if @chat_message.nil? || + @chat_message.revisions.where("created_at > ?", args[:timestamp]).any? + return + end @creator = @chat_message.user @chat_channel = @chat_message.chat_channel @@ -13,9 +18,7 @@ def execute(args = {}) @already_notified_user_ids = args[:already_notified_user_ids] || [] user_ids_to_notify = args[:to_notify_ids_map] || {} - user_ids_to_notify.each do |mention_type, ids| - process_mentions(ids, mention_type.to_sym) - end + user_ids_to_notify.each { |mention_type, ids| process_mentions(ids, mention_type.to_sym) } end private @@ -24,7 +27,7 @@ def get_memberships(user_ids) UserChatChannelMembership.includes(:user).where( user_id: (user_ids - @already_notified_user_ids), chat_channel_id: @chat_message.chat_channel_id, - following: true + following: true, ) end @@ -33,17 +36,19 @@ def build_data_for(membership, identifier_type:) chat_message_id: @chat_message.id, chat_channel_id: @chat_channel.id, mentioned_by_username: @creator.username, - is_direct_message_channel: @is_direct_message_channel + is_direct_message_channel: @is_direct_message_channel, } - data[:chat_channel_title] = @chat_channel.title(membership.user) unless @is_direct_message_channel + data[:chat_channel_title] = @chat_channel.title( + membership.user, + ) unless @is_direct_message_channel return data if identifier_type == :direct_mentions case identifier_type when :here_mentions - data[:identifier] = 'here' + data[:identifier] = "here" when :global_mentions - data[:identifier] = 'all' + data[:identifier] = "all" else data[:is_group_mention] = true end @@ -57,28 +62,36 @@ def build_payload_for(membership, identifier_type:) username: @creator.username, tag: DiscourseChat::ChatNotifier.push_notification_tag(:mention, @chat_channel.id), excerpt: @chat_message.push_notification_excerpt, - post_url: "/chat/channel/#{@chat_channel.id}/#{@chat_channel.title(membership.user)}?messageId=#{@chat_message.id}" + post_url: + "/chat/channel/#{@chat_channel.id}/#{@chat_channel.title(membership.user)}?messageId=#{@chat_message.id}", } - translation_prefix = @is_direct_message_channel ? - "discourse_push_notifications.popup.direct_message_chat_mention" : - "discourse_push_notifications.popup.chat_mention" + translation_prefix = + ( + if @is_direct_message_channel + "discourse_push_notifications.popup.direct_message_chat_mention" + else + "discourse_push_notifications.popup.chat_mention" + end + ) translation_suffix = identifier_type == :direct_mentions ? "direct" : "other" - identifier_text = case identifier_type - when :here_mentions - '@here' - when :global_mentions - '@all' - when :direct_mentions - '' + identifier_text = + case identifier_type + when :here_mentions + "@here" + when :global_mentions + "@all" + when :direct_mentions + "" else - "@#{identifier_type}" - end + "@#{identifier_type}" + end - payload[:translated_title] = I18n.t("#{translation_prefix}.#{translation_suffix}", + payload[:translated_title] = I18n.t( + "#{translation_prefix}.#{translation_suffix}", username: @creator.username, identifier: identifier_text, - channel: @chat_channel.title(membership.user) + channel: @chat_channel.title(membership.user), ) payload @@ -87,21 +100,30 @@ def build_payload_for(membership, identifier_type:) def create_notification!(membership, notification_data) is_read = DiscourseChat::ChatNotifier.user_has_seen_message?(membership, @chat_message.id) - notification = Notification.create!( - notification_type: Notification.types[:chat_mention], - user_id: membership.user_id, - high_priority: true, - data: notification_data.to_json, - read: is_read + notification = + Notification.create!( + notification_type: Notification.types[:chat_mention], + user_id: membership.user_id, + high_priority: true, + data: notification_data.to_json, + read: is_read, + ) + ChatMention.create!( + notification: notification, + user: membership.user, + chat_message: @chat_message, ) - ChatMention.create!(notification: notification, user: membership.user, chat_message: @chat_message) end def send_notifications(membership, notification_data, os_payload) create_notification!(membership, notification_data) if !membership.desktop_notifications_never? - MessageBus.publish("/chat/notification-alert/#{membership.user_id}", os_payload, user_ids: [membership.user_id]) + MessageBus.publish( + "/chat/notification-alert/#{membership.user_id}", + os_payload, + user_ids: [membership.user_id], + ) end if !membership.mobile_notifications_never? diff --git a/app/jobs/regular/chat_notify_watching.rb b/app/jobs/regular/chat_notify_watching.rb index 27c86e204..0dfac3d33 100644 --- a/app/jobs/regular/chat_notify_watching.rb +++ b/app/jobs/regular/chat_notify_watching.rb @@ -3,7 +3,8 @@ module Jobs class ChatNotifyWatching < ::Jobs::Base def execute(args = {}) - @chat_message = ChatMessage.includes(:user, chat_channel: :chatable).find_by(id: args[:chat_message_id]) + @chat_message = + ChatMessage.includes(:user, chat_channel: :chatable).find_by(id: args[:chat_message_id]) return if @chat_message.nil? @creator = @chat_message.user @@ -19,12 +20,13 @@ def execute(args = {}) .where.not(user_id: args[:except_user_ids]) .where(chat_channel_id: @chat_channel.id) .where(following: true) - .where("desktop_notification_level = ? OR mobile_notification_level = ?", - always_notification_level, always_notification_level) + .where( + "desktop_notification_level = ? OR mobile_notification_level = ?", + always_notification_level, + always_notification_level, + ) .merge(User.not_suspended) - .each do |membership| - send_notifications(membership) - end + .each { |membership| send_notifications(membership) } end def send_notifications(membership) @@ -34,9 +36,14 @@ def send_notifications(membership) return if DiscourseChat::ChatNotifier.user_has_seen_message?(membership, @chat_message.id) return if online_user_ids.include?(user.id) - translation_key = @is_direct_message_channel ? - "discourse_push_notifications.popup.new_direct_chat_message" : - "discourse_push_notifications.popup.new_chat_message" + translation_key = + ( + if @is_direct_message_channel + "discourse_push_notifications.popup.new_direct_chat_message" + else + "discourse_push_notifications.popup.new_chat_message" + end + ) translation_args = { username: @creator.username } translation_args[:channel] = @chat_channel.title(user) unless @is_direct_message_channel @@ -47,16 +54,14 @@ def send_notifications(membership) post_url: "/chat/channel/#{@chat_channel.id}/#{@chat_channel.title(user)}", translated_title: I18n.t(translation_key, translation_args), tag: DiscourseChat::ChatNotifier.push_notification_tag(:message, @chat_channel.id), - excerpt: @chat_message.push_notification_excerpt + excerpt: @chat_message.push_notification_excerpt, } if membership.desktop_notifications_always? MessageBus.publish("/chat/notification-alert/#{user.id}", payload, user_ids: [user.id]) end - if membership.mobile_notifications_always? - PostAlerter.push_notification(user, payload) - end + PostAlerter.push_notification(user, payload) if membership.mobile_notifications_always? end def online_user_ids diff --git a/app/jobs/regular/process_chat_message.rb b/app/jobs/regular/process_chat_message.rb index 7c161a944..a24e14f88 100644 --- a/app/jobs/regular/process_chat_message.rb +++ b/app/jobs/regular/process_chat_message.rb @@ -3,17 +3,17 @@ module Jobs class ProcessChatMessage < ::Jobs::Base def execute(args = {}) - DistributedMutex.synchronize("process_chat_message_#{args[:chat_message_id]}", validity: 10.minutes) do + DistributedMutex.synchronize( + "process_chat_message_#{args[:chat_message_id]}", + validity: 10.minutes, + ) do chat_message = ChatMessage.find_by(id: args[:chat_message_id]) return if !chat_message processor = DiscourseChat::ChatMessageProcessor.new(chat_message) processor.run! if args[:is_dirty] || processor.dirty? - chat_message.update( - cooked: processor.html, - cooked_version: ChatMessage::BAKED_VERSION - ) + chat_message.update(cooked: processor.html, cooked_version: ChatMessage::BAKED_VERSION) ChatPublisher.publish_processed!(chat_message) end end diff --git a/app/jobs/scheduled/delete_old_chat_messages.rb b/app/jobs/scheduled/delete_old_chat_messages.rb index d0c0fb964..025c9d2f1 100644 --- a/app/jobs/scheduled/delete_old_chat_messages.rb +++ b/app/jobs/scheduled/delete_old_chat_messages.rb @@ -15,7 +15,8 @@ def delete_public_channel_messages ChatMessage .in_public_channel .created_before(SiteSetting.chat_channel_retention_days.days.ago) - .in_batches(of: 200).each do |relation| + .in_batches(of: 200) + .each do |relation| destroyed_ids = relation.destroy_all.pluck(:id) reset_last_read_message_id(destroyed_ids) end @@ -27,7 +28,8 @@ def delete_dm_channel_messages ChatMessage .in_dm_channel .created_before(SiteSetting.chat_dm_retention_days.days.ago) - .in_batches(of: 200).each do |relation| + .in_batches(of: 200) + .each do |relation| destroyed_ids = relation.destroy_all.pluck(:id) reset_last_read_message_id(destroyed_ids) end @@ -38,9 +40,9 @@ def valid_day_value?(setting_name) end def reset_last_read_message_id(ids) - UserChatChannelMembership - .where(last_read_message_id: ids) - .update_all(last_read_message_id: nil) + UserChatChannelMembership.where(last_read_message_id: ids).update_all( + last_read_message_id: nil, + ) end end end diff --git a/app/jobs/scheduled/update_user_counts_for_chat_channels.rb b/app/jobs/scheduled/update_user_counts_for_chat_channels.rb index f42206caa..3c5999842 100644 --- a/app/jobs/scheduled/update_user_counts_for_chat_channels.rb +++ b/app/jobs/scheduled/update_user_counts_for_chat_channels.rb @@ -5,19 +5,18 @@ class UpdateUserCountsForChatChannels < ::Jobs::Scheduled every 2.hours def execute(args = {}) - ChatChannel.find_each do |chat_channel| - set_user_count(chat_channel) - end + ChatChannel.find_each { |chat_channel| set_user_count(chat_channel) } end def set_user_count(chat_channel) current_count = chat_channel.user_count || 0 - new_count = chat_channel - .user_chat_channel_memberships - .joins(:user) - .where(following: true) - .merge(User.activated.not_suspended.not_staged) - .count + new_count = + chat_channel + .user_chat_channel_memberships + .joins(:user) + .where(following: true) + .merge(User.activated.not_suspended.not_staged) + .count return if current_count == new_count diff --git a/app/models/chat_channel.rb b/app/models/chat_channel.rb index 148dc4411..70f6b067a 100644 --- a/app/models/chat_channel.rb +++ b/app/models/chat_channel.rb @@ -3,34 +3,38 @@ class ChatChannel < ActiveRecord::Base include Trashable attribute :muted, default: false - attribute :desktop_notification_level, default: UserChatChannelMembership::DEFAULT_NOTIFICATION_LEVEL - attribute :mobile_notification_level, default: UserChatChannelMembership::DEFAULT_NOTIFICATION_LEVEL + attribute :desktop_notification_level, + default: UserChatChannelMembership::DEFAULT_NOTIFICATION_LEVEL + attribute :mobile_notification_level, + default: UserChatChannelMembership::DEFAULT_NOTIFICATION_LEVEL attribute :following, default: false attribute :unread_count, default: 0 attribute :unread_mentions, default: 0 attribute :last_read_message_id, default: nil belongs_to :chatable, polymorphic: true - belongs_to :direct_message_channel, -> { where(chat_channels: { chatable_type: 'DirectMessageChannel' }) }, foreign_key: 'chatable_id' + belongs_to :direct_message_channel, + -> { where(chat_channels: { chatable_type: "DirectMessageChannel" }) }, + foreign_key: "chatable_id" has_many :chat_messages has_many :user_chat_channel_memberships has_one :chat_channel_archive - enum status: { - open: 0, - read_only: 1, - closed: 2, - archived: 3 - }, _scopes: false + enum status: { open: 0, read_only: 1, closed: 2, archived: 3 }, _scopes: false - validates :name, length: { maximum: Proc.new { SiteSetting.max_topic_title_length } }, presence: true, allow_nil: true + validates :name, + length: { + maximum: Proc.new { SiteSetting.max_topic_title_length }, + }, + presence: true, + allow_nil: true def add(user) ActiveRecord::Base.transaction do - membership = UserChatChannelMembership - .find_or_initialize_by(user_id: user.id, chat_channel: self) + membership = + UserChatChannelMembership.find_or_initialize_by(user_id: user.id, chat_channel: self) if !membership.following membership.following = true @@ -44,8 +48,7 @@ def add(user) def remove(user) ActiveRecord::Base.transaction do - membership = UserChatChannelMembership - .find_by!(user_id: user.id, chat_channel: self) + membership = UserChatChannelMembership.find_by!(user_id: user.id, chat_channel: self) if membership.following membership.update!(following: false) @@ -104,17 +107,11 @@ def chatable_has_custom_fields? end def allowed_user_ids - direct_message_channel? ? - chatable.user_ids : - nil + direct_message_channel? ? chatable.user_ids : nil end def allowed_group_ids - if category_channel? - chatable.secure_group_ids - else - nil - end + category_channel? ? chatable.secure_group_ids : nil end def public_channel_title @@ -145,7 +142,7 @@ def change_status(acting_user, target_status) log_channel_status_change( acting_user: acting_user, new_status: target_status, - old_status: old_status + old_status: old_status, ) end @@ -174,9 +171,9 @@ def self.public_channel_chatable_types end def self.public_channels - where(chatable_type: public_channel_chatable_types) - .where("categories.id IS NOT NULL") - .joins("LEFT JOIN categories ON categories.id = chat_channels.chatable_id AND chat_channels.chatable_type = 'Category'") + where(chatable_type: public_channel_chatable_types).where("categories.id IS NOT NULL").joins( + "LEFT JOIN categories ON categories.id = chat_channels.chatable_id AND chat_channels.chatable_type = 'Category'", + ) end private @@ -189,7 +186,7 @@ def log_channel_status_change(acting_user:, new_status:, old_status:) :chat_channel_status_change, channel: self, old_status: old_status, - new_status: new_status + new_status: new_status, ) StaffActionLogger.new(acting_user).log_custom( @@ -198,8 +195,8 @@ def log_channel_status_change(acting_user:, new_status:, old_status:) chat_channel_id: self.id, chat_channel_name: self.name, previous_value: old_status, - new_value: new_status - } + new_value: new_status, + }, ) ChatPublisher.publish_channel_status(self) diff --git a/app/models/chat_channel_archive.rb b/app/models/chat_channel_archive.rb index 2c647e396..e84cdb35e 100644 --- a/app/models/chat_channel_archive.rb +++ b/app/models/chat_channel_archive.rb @@ -2,13 +2,12 @@ class ChatChannelArchive < ActiveRecord::Base belongs_to :chat_channel - belongs_to :archived_by, class_name: 'User' + belongs_to :archived_by, class_name: "User" - belongs_to :destination_topic, class_name: 'Topic' + belongs_to :destination_topic, class_name: "Topic" def complete? - self.archived_messages >= self.total_messages && - self.chat_channel.chat_messages.count.zero? + self.archived_messages >= self.total_messages && self.chat_channel.chat_messages.count.zero? end def failed? diff --git a/app/models/chat_message.rb b/app/models/chat_message.rb index 326db01fb..b61904465 100644 --- a/app/models/chat_message.rb +++ b/app/models/chat_message.rb @@ -18,19 +18,19 @@ class ChatMessage < ActiveRecord::Base has_one :chat_webhook_event, dependent: :destroy has_one :chat_mention, dependent: :destroy - scope :in_public_channel, -> { - joins(:chat_channel) - .where(chat_channel: { chatable_type: ChatChannel.public_channel_chatable_types }) - } + scope :in_public_channel, + -> { + joins(:chat_channel).where( + chat_channel: { + chatable_type: ChatChannel.public_channel_chatable_types, + }, + ) + } - scope :in_dm_channel, -> { - joins(:chat_channel) - .where(chat_channel: { chatable_type: "DirectMessageChannel" }) - } + scope :in_dm_channel, + -> { joins(:chat_channel).where(chat_channel: { chatable_type: "DirectMessageChannel" }) } - scope :created_before, -> (date) { - where("chat_messages.created_at < ?", date) - } + scope :created_before, ->(date) { where("chat_messages.created_at < ?", date) } def validate_message(has_uploads:) WatchedWordsValidator.new(attributes: [:message]).validate(self) @@ -39,7 +39,10 @@ def validate_message(has_uploads:) if !has_uploads && message_too_short? self.errors.add( :base, - I18n.t("chat.errors.minimum_length_not_met", minimum: SiteSetting.chat_minimum_message_length) + I18n.t( + "chat.errors.minimum_length_not_met", + minimum: SiteSetting.chat_minimum_message_length, + ), ) end end @@ -48,14 +51,10 @@ def attach_uploads(uploads) return if uploads.blank? now = Time.now - record_attrs = uploads.map do |upload| - { - upload_id: upload.id, - chat_message_id: self.id, - created_at: now, - updated_at: now - } - end + record_attrs = + uploads.map do |upload| + { upload_id: upload.id, chat_message_id: self.id, created_at: now, updated_at: now } + end ChatUpload.insert_all!(record_attrs) end @@ -79,16 +78,9 @@ def push_notification_excerpt end def add_flag(user) - reviewable = ReviewableChatMessage.needs_review!( - created_by: user, - target: self, - ) + reviewable = ReviewableChatMessage.needs_review!(created_by: user, target: self) reviewable.update(target_created_by: self.user) - reviewable.add_score( - user, - ReviewableScore.types[:needs_review], - force_review: true - ) + reviewable.add_score(user, ReviewableScore.types[:needs_review], force_review: true) reviewable end @@ -102,16 +94,13 @@ def to_markdown if self.message.present? msg = self.message - if self.chat_uploads.any? - markdown << msg + "\n" - else - markdown << msg - end + self.chat_uploads.any? ? markdown << msg + "\n" : markdown << msg end - self.chat_uploads.order(:created_at).each do |chat_upload| - markdown << UploadMarkdown.new(chat_upload.upload).to_markdown - end + self + .chat_uploads + .order(:created_at) + .each { |chat_upload| markdown << UploadMarkdown.new(chat_upload.upload).to_markdown } markdown.reject(&:empty?).join("\n") end @@ -124,13 +113,8 @@ def cook def rebake!(invalidate_oneboxes: false, priority: nil) previous_cooked = self.cooked new_cooked = self.class.cook(message, invalidate_oneboxes: invalidate_oneboxes) - update_columns( - cooked: new_cooked, - cooked_version: BAKED_VERSION - ) - args = { - chat_message_id: self.id, - } + update_columns(cooked: new_cooked, cooked_version: BAKED_VERSION) + args = { chat_message_id: self.id } args[:queue] = priority.to_s if priority && priority != :normal args[:is_dirty] = true if previous_cooked != new_cooked @@ -138,10 +122,10 @@ def rebake!(invalidate_oneboxes: false, priority: nil) end def self.uncooked - where('cooked_version <> ? or cooked_version IS NULL', BAKED_VERSION) + where("cooked_version <> ? or cooked_version IS NULL", BAKED_VERSION) end - MARKDOWN_FEATURES = %w{ + MARKDOWN_FEATURES = %w[ anchor bbcode-block bbcode-inline @@ -162,9 +146,9 @@ def self.uncooked text-post-process upload-protocol watched-words - } + ] - MARKDOWN_IT_RULES = %w{ + MARKDOWN_IT_RULES = %w[ autolink list backticks @@ -178,24 +162,26 @@ def self.uncooked strikethrough blockquote emphasis - } + ] def self.cook(message, opts = {}) - cooked = PrettyText.cook( - message, - features_override: MARKDOWN_FEATURES + DiscoursePluginRegistry.chat_markdown_features.to_a, - markdown_it_rules: MARKDOWN_IT_RULES, - force_quote_link: true - ) - - result = Oneboxer.apply(cooked) do |url| - if opts[:invalidate_oneboxes] - Oneboxer.invalidate(url) - InlineOneboxer.invalidate(url) + cooked = + PrettyText.cook( + message, + features_override: MARKDOWN_FEATURES + DiscoursePluginRegistry.chat_markdown_features.to_a, + markdown_it_rules: MARKDOWN_IT_RULES, + force_quote_link: true, + ) + + result = + Oneboxer.apply(cooked) do |url| + if opts[:invalidate_oneboxes] + Oneboxer.invalidate(url) + InlineOneboxer.invalidate(url) + end + onebox = Oneboxer.cached_onebox(url) + onebox end - onebox = Oneboxer.cached_onebox(url) - onebox - end cooked = result.to_html if result.changed? cooked diff --git a/app/models/chat_view.rb b/app/models/chat_view.rb index ad9e9b724..9df0df18d 100644 --- a/app/models/chat_view.rb +++ b/app/models/chat_view.rb @@ -3,7 +3,13 @@ class ChatView attr_reader :user, :chat_channel, :chat_messages, :can_load_more_past, :can_load_more_future - def initialize(chat_channel:, chat_messages:, user:, can_load_more_past: nil, can_load_more_future: nil) + def initialize( + chat_channel:, + chat_messages:, + user:, + can_load_more_past: nil, + can_load_more_future: nil + ) @chat_channel = chat_channel @chat_messages = chat_messages @user = user @@ -44,13 +50,13 @@ def get_reviewable_ids ids = {} - DB.query( - sql, - pending: ReviewableScore.statuses[:pending], - message_ids: @chat_messages.map(&:id) - ).each do |row| - ids[row.target_id] = row.reviewable_id - end + DB + .query( + sql, + pending: ReviewableScore.statuses[:pending], + message_ids: @chat_messages.map(&:id), + ) + .each { |row| ids[row.target_id] = row.reviewable_id } ids end @@ -72,13 +78,9 @@ def get_user_flag_statuses statuses = {} - DB.query( - sql, - message_ids: @chat_messages.map(&:id), - user_id: @user.id - ).each do |row| - statuses[row.target_id] = row.status - end + DB + .query(sql, message_ids: @chat_messages.map(&:id), user_id: @user.id) + .each { |row| statuses[row.target_id] = row.status } statuses end diff --git a/app/models/direct_message_channel.rb b/app/models/direct_message_channel.rb index a7b01758b..7a4bdcdf8 100644 --- a/app/models/direct_message_channel.rb +++ b/app/models/direct_message_channel.rb @@ -12,17 +12,21 @@ def chat_channel_title_for_user(chat_channel, acting_user) users = direct_message_users.map(&:user) - [acting_user] # direct message to self - return I18n.t("chat.channel.dm_title.single_user", user: "@#{acting_user.username}") if users.empty? + if users.empty? + return I18n.t("chat.channel.dm_title.single_user", user: "@#{acting_user.username}") + end # all users deleted return chat_channel.id if !users.first usernames_formatted = users.sort_by(&:username).map { |u| "@#{u.username}" } if usernames_formatted.size > 5 - return I18n.t( - "chat.channel.dm_title.multi_user_truncated", - users: usernames_formatted[0..4].join(", "), - leftover: usernames_formatted.length - 5 + return( + I18n.t( + "chat.channel.dm_title.multi_user_truncated", + users: usernames_formatted[0..4].join(", "), + leftover: usernames_formatted.length - 5, + ) ) end diff --git a/app/models/incoming_chat_webhook.rb b/app/models/incoming_chat_webhook.rb index 9045650f0..e71b539a0 100644 --- a/app/models/incoming_chat_webhook.rb +++ b/app/models/incoming_chat_webhook.rb @@ -4,9 +4,7 @@ class IncomingChatWebhook < ActiveRecord::Base belongs_to :chat_channel has_many :chat_webhook_events - before_create do - self.key = SecureRandom.hex(12) - end + before_create { self.key = SecureRandom.hex(12) } def url "#{Discourse.base_url}/chat/hooks/#{key}.json" diff --git a/app/models/reviewable_chat_message.rb b/app/models/reviewable_chat_message.rb index df1b6850b..6d9f25e12 100644 --- a/app/models/reviewable_chat_message.rb +++ b/app/models/reviewable_chat_message.rb @@ -1,9 +1,8 @@ # frozen_string_literal: true -require_dependency 'reviewable' +require_dependency "reviewable" class ReviewableChatMessage < Reviewable - def self.on_score_updated(reviewable) # Silence user if new score is over the `score_to_silence_user` return if reviewable.type != self.name @@ -19,16 +18,18 @@ def self.on_score_updated(reviewable) user, Discourse.system_user, silenced_till: auto_silence_duration.minutes.from_now, - reason: I18n.t("chat.errors.auto_silence_from_flags") + reason: I18n.t("chat.errors.auto_silence_from_flags"), ) end def self.action_aliases - { agree_and_keep_hidden: :agree_and_delete, + { + agree_and_keep_hidden: :agree_and_delete, agree_and_silence: :agree_and_delete, agree_and_suspend: :agree_and_delete, delete_and_agree: :agree_and_delete, - disagree_and_restore: :disagree } + disagree_and_restore: :disagree, + } end def self.score_to_silence_user @@ -55,28 +56,39 @@ def build_actions(actions, guardian, args) return unless pending? return if chat_message.blank? - agree = actions.add_bundle("#{id}-agree", icon: 'thumbs-up', label: 'reviewables.actions.agree.title') + agree = + actions.add_bundle("#{id}-agree", icon: "thumbs-up", label: "reviewables.actions.agree.title") if chat_message.deleted_at? - build_action(actions, :agree_and_restore, icon: 'far-eye', bundle: agree) - build_action(actions, :agree_and_keep_deleted, icon: 'thumbs-up', bundle: agree) - build_action(actions, :disagree_and_restore, icon: 'thumbs-down') + build_action(actions, :agree_and_restore, icon: "far-eye", bundle: agree) + build_action(actions, :agree_and_keep_deleted, icon: "thumbs-up", bundle: agree) + build_action(actions, :disagree_and_restore, icon: "thumbs-down") else - build_action(actions, :agree_and_delete, icon: 'far-eye-slash', bundle: agree) - build_action(actions, :agree_and_keep_message, icon: 'thumbs-up', bundle: agree) - build_action(actions, :disagree, icon: 'thumbs-down') + build_action(actions, :agree_and_delete, icon: "far-eye-slash", bundle: agree) + build_action(actions, :agree_and_keep_message, icon: "thumbs-up", bundle: agree) + build_action(actions, :disagree, icon: "thumbs-down") end if guardian.can_suspend?(chat_message_creator) - build_action(actions, :agree_and_suspend, icon: 'ban', bundle: agree, client_action: 'suspend') - build_action(actions, :agree_and_silence, icon: 'microphone-slash', bundle: agree, client_action: 'silence') + build_action( + actions, + :agree_and_suspend, + icon: "ban", + bundle: agree, + client_action: "suspend", + ) + build_action( + actions, + :agree_and_silence, + icon: "microphone-slash", + bundle: agree, + client_action: "silence", + ) end - build_action(actions, :ignore, icon: 'external-link-alt') + build_action(actions, :ignore, icon: "external-link-alt") - unless chat_message.deleted_at? - build_action(actions, :delete_and_agree, icon: 'far-trash-alt') - end + build_action(actions, :delete_and_agree, icon: "far-trash-alt") unless chat_message.deleted_at? end def perform_agree_and_keep_message(performed_by, args) @@ -84,21 +96,15 @@ def perform_agree_and_keep_message(performed_by, args) end def perform_agree_and_restore(performed_by, args) - agree do - chat_message.recover! - end + agree { chat_message.recover! } end def perform_agree_and_delete(performed_by, args) - agree do - chat_message.trash!(performed_by) - end + agree { chat_message.trash!(performed_by) } end def perform_disagree_and_restore(performed_by, args) - disagree do - chat_message.recover! - end + disagree { chat_message.recover! } end def perform_disagree(performed_by, args) @@ -110,9 +116,7 @@ def perform_ignore(performed_by, args) end def perform_delete_and_ignore(performed_by, args) - ignore do - chat_message.trash!(performed_by) - end + ignore { chat_message.trash!(performed_by) } end private @@ -140,7 +144,15 @@ def ignore end end - def build_action(actions, id, icon:, button_class: nil, bundle: nil, client_action: nil, confirm: false) + def build_action( + actions, + id, + icon:, + button_class: nil, + bundle: nil, + client_action: nil, + confirm: false + ) actions.add(id, bundle: bundle) do |action| prefix = "reviewables.actions.#{id}" action.icon = icon diff --git a/app/models/user_chat_channel_membership.rb b/app/models/user_chat_channel_membership.rb index ca31d3a94..f9170739b 100644 --- a/app/models/user_chat_channel_membership.rb +++ b/app/models/user_chat_channel_membership.rb @@ -6,24 +6,12 @@ class UserChatChannelMembership < ActiveRecord::Base belongs_to :last_read_message, class_name: "ChatMessage", optional: true DEFAULT_NOTIFICATION_LEVEL = :mention - NOTIFICATION_LEVELS = { - never: 0, - mention: 1, - always: 2 - } - VALIDATED_ATTRS = [ - :following, - :muted, - :desktop_notification_level, - :mobile_notification_level - ] + NOTIFICATION_LEVELS = { never: 0, mention: 1, always: 2 } + VALIDATED_ATTRS = %i[following muted desktop_notification_level mobile_notification_level] enum desktop_notification_level: NOTIFICATION_LEVELS, _prefix: :desktop_notifications enum mobile_notification_level: NOTIFICATION_LEVELS, _prefix: :mobile_notifications - enum join_mode: { - manual: 0, - automatic: 1 - } + enum join_mode: { manual: 0, automatic: 1 } validate :changes_for_direct_message_channels @@ -35,7 +23,9 @@ def enforce_automatic_channel_memberships(channel) def enforce_automatic_user_membership(channel, user) Jobs.enqueue( :auto_join_channel_batch, - chat_channel_id: channel.id, starts_at: user.id, ends_at: user.id + chat_channel_id: channel.id, + starts_at: user.id, + ends_at: user.id, ) end end @@ -43,7 +33,8 @@ def enforce_automatic_user_membership(channel, user) private def changes_for_direct_message_channels - needs_validation = VALIDATED_ATTRS.any? { |attr| changed_attribute_names_to_save.include?(attr.to_s) } + needs_validation = + VALIDATED_ATTRS.any? { |attr| changed_attribute_names_to_save.include?(attr.to_s) } if needs_validation && chat_channel.direct_message_channel? errors.add(:muted) if muted errors.add(:desktop_notification_level) if desktop_notification_level.to_sym != :always diff --git a/app/queries/chat_channel_memberships_query.rb b/app/queries/chat_channel_memberships_query.rb index 5c1cb37fc..c21a6e123 100644 --- a/app/queries/chat_channel_memberships_query.rb +++ b/app/queries/chat_channel_memberships_query.rb @@ -2,29 +2,31 @@ class ChatChannelMembershipsQuery def self.call(channel, limit: 50, offset: 0, username: nil) - query = UserChatChannelMembership - .includes(:user) - .where(user: User.activated.not_suspended.not_staged) - .where(chat_channel: channel, following: true) + query = + UserChatChannelMembership + .includes(:user) + .where(user: User.activated.not_suspended.not_staged) + .where(chat_channel: channel, following: true) if username.present? if SiteSetting.prioritize_username_in_ux || !SiteSetting.enable_names - query = query - .where('users.username_lower ILIKE ?', "%#{username}%") + query = query.where("users.username_lower ILIKE ?", "%#{username}%") else - query = query - .where('LOWER(users.name) ILIKE ? OR users.username_lower ILIKE ?', "%#{username}%", "%#{username}%") + query = + query.where( + "LOWER(users.name) ILIKE ? OR users.username_lower ILIKE ?", + "%#{username}%", + "%#{username}%", + ) end end if SiteSetting.prioritize_username_in_ux || !SiteSetting.enable_names - query = query.order('users.username_lower ASC') + query = query.order("users.username_lower ASC") else - query = query.order('users.name ASC, users.username_lower ASC') + query = query.order("users.name ASC, users.username_lower ASC") end - query - .offset(offset) - .limit(limit) + query.offset(offset).limit(limit) end end diff --git a/app/serializers/chat_channel_settings_serializer.rb b/app/serializers/chat_channel_settings_serializer.rb index 3d2a502a8..04f16a72f 100644 --- a/app/serializers/chat_channel_settings_serializer.rb +++ b/app/serializers/chat_channel_settings_serializer.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true class ChatChannelSettingsSerializer < ChatChannelSerializer - attributes :desktop_notification_level, - :mobile_notification_level, - :following + attributes :desktop_notification_level, :mobile_notification_level, :following end diff --git a/app/serializers/chat_message_serializer.rb b/app/serializers/chat_message_serializer.rb index cefae8973..97231bb37 100644 --- a/app/serializers/chat_message_serializer.rb +++ b/app/serializers/chat_message_serializer.rb @@ -2,17 +2,17 @@ class ChatMessageSerializer < ApplicationSerializer attributes :id, - :message, - :cooked, - :created_at, - :excerpt, - :deleted_at, - :deleted_by_id, - :reviewable_id, - :user_flag_status, - :edited, - :reactions, - :bookmark + :message, + :cooked, + :created_at, + :excerpt, + :deleted_at, + :deleted_by_id, + :reviewable_id, + :user_flag_status, + :edited, + :reactions, + :bookmark has_one :user, serializer: BasicUserSerializer, embed: :objects has_one :chat_webhook_event, serializer: ChatWebhookEventSerializer, embed: :objects @@ -21,17 +21,21 @@ class ChatMessageSerializer < ApplicationSerializer def reactions reactions_hash = {} - object.reactions.group_by(&:emoji).each do |emoji, reactions| - users = reactions[0..6].map(&:user).filter { |user| user.id != scope&.user&.id }[0..5] - - next unless Emoji.exists?(emoji) - - reactions_hash[emoji] = { - count: reactions.count, - users: ActiveModel::ArraySerializer.new(users, each_serializer: BasicUserSerializer).as_json, - reacted: users_reactions.include?(emoji) - } - end + object + .reactions + .group_by(&:emoji) + .each do |emoji, reactions| + users = reactions[0..6].map(&:user).filter { |user| user.id != scope&.user&.id }[0..5] + + next unless Emoji.exists?(emoji) + + reactions_hash[emoji] = { + count: reactions.count, + users: + ActiveModel::ArraySerializer.new(users, each_serializer: BasicUserSerializer).as_json, + reacted: users_reactions.include?(emoji), + } + end reactions_hash end @@ -40,7 +44,8 @@ def include_reactions? end def users_reactions - @users_reactions ||= object.reactions.select { |reaction| reaction.user_id == scope&.user&.id }.map(&:emoji) + @users_reactions ||= + object.reactions.select { |reaction| reaction.user_id == scope&.user&.id }.map(&:emoji) end def users_bookmark @@ -58,7 +63,7 @@ def bookmark name: users_bookmark.name, auto_delete_preference: users_bookmark.auto_delete_preference, bookmarkable_id: users_bookmark.bookmarkable_id, - bookmarkable_type: users_bookmark.bookmarkable_type + bookmarkable_type: users_bookmark.bookmarkable_type, } end diff --git a/app/serializers/chat_view_serializer.rb b/app/serializers/chat_view_serializer.rb index fc878579e..ef4f1061c 100644 --- a/app/serializers/chat_view_serializer.rb +++ b/app/serializers/chat_view_serializer.rb @@ -9,7 +9,7 @@ def chat_messages each_serializer: ChatMessageSerializer, reviewable_ids: object.reviewable_ids, user_flag_statuses: object.user_flag_statuses, - scope: scope + scope: scope, ) end @@ -23,7 +23,9 @@ def meta can_delete_others: scope.can_delete_other_chats?(object.chat_channel.chatable), } meta_hash[:can_load_more_past] = object.can_load_more_past unless object.can_load_more_past.nil? - meta_hash[:can_load_more_future] = object.can_load_more_future unless object.can_load_more_future.nil? + meta_hash[ + :can_load_more_future + ] = object.can_load_more_future unless object.can_load_more_future.nil? meta_hash end end diff --git a/app/serializers/chat_webhook_event_serializer.rb b/app/serializers/chat_webhook_event_serializer.rb index 915457d87..3fb674c65 100644 --- a/app/serializers/chat_webhook_event_serializer.rb +++ b/app/serializers/chat_webhook_event_serializer.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true class ChatWebhookEventSerializer < ApplicationSerializer - attributes :username, - :emoji + attributes :username, :emoji end diff --git a/app/serializers/incoming_chat_webhook_serializer.rb b/app/serializers/incoming_chat_webhook_serializer.rb index 28fc2c63a..7f097e62b 100644 --- a/app/serializers/incoming_chat_webhook_serializer.rb +++ b/app/serializers/incoming_chat_webhook_serializer.rb @@ -3,11 +3,5 @@ class IncomingChatWebhookSerializer < ApplicationSerializer has_one :chat_channel, serializer: ChatChannelSerializer, embed: :objects - attributes :id, - :name, - :description, - :emoji, - :url, - :username, - :updated_at + attributes :id, :name, :description, :emoji, :url, :username, :updated_at end diff --git a/app/serializers/reviewable_chat_message_serializer.rb b/app/serializers/reviewable_chat_message_serializer.rb index 36236e4f5..3132b04b9 100644 --- a/app/serializers/reviewable_chat_message_serializer.rb +++ b/app/serializers/reviewable_chat_message_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_dependency 'reviewable_serializer' +require_dependency "reviewable_serializer" class ReviewableChatMessageSerializer < ReviewableSerializer has_one :chat_message, serializer: ChatMessageSerializer, root: false, embed: :objects diff --git a/app/services/chat_publisher.rb b/app/services/chat_publisher.rb index 45bbbc114..1851651f3 100644 --- a/app/services/chat_publisher.rb +++ b/app/services/chat_publisher.rb @@ -2,12 +2,20 @@ module ChatPublisher def self.publish_new!(chat_channel, chat_message, staged_id) - content = ChatMessageSerializer.new(chat_message, { scope: anonymous_guardian, root: :chat_message }).as_json + content = + ChatMessageSerializer.new( + chat_message, + { scope: anonymous_guardian, root: :chat_message }, + ).as_json content[:type] = :sent content[:stagedId] = staged_id permissions = permissions(chat_channel) MessageBus.publish("/chat/#{chat_channel.id}", content.as_json, permissions) - MessageBus.publish("/chat/#{chat_channel.id}/new-messages", { message_id: chat_message.id, user_id: chat_message.user_id }, permissions) + MessageBus.publish( + "/chat/#{chat_channel.id}/new-messages", + { message_id: chat_message.id, user_id: chat_message.user_id }, + permissions, + ) end def self.publish_processed!(chat_message) @@ -16,20 +24,28 @@ def self.publish_processed!(chat_message) type: :processed, chat_message: { id: chat_message.id, - cooked: chat_message.cooked - } + cooked: chat_message.cooked, + }, } MessageBus.publish("/chat/#{chat_channel.id}", content.as_json, permissions(chat_channel)) end def self.publish_edit!(chat_channel, chat_message) - content = ChatMessageSerializer.new(chat_message, { scope: anonymous_guardian, root: :chat_message }).as_json + content = + ChatMessageSerializer.new( + chat_message, + { scope: anonymous_guardian, root: :chat_message }, + ).as_json content[:type] = :edit MessageBus.publish("/chat/#{chat_channel.id}", content.as_json, permissions(chat_channel)) end def self.publish_refresh!(chat_channel, chat_message) - content = ChatMessageSerializer.new(chat_message, { scope: anonymous_guardian, root: :chat_message }).as_json + content = + ChatMessageSerializer.new( + chat_message, + { scope: anonymous_guardian, root: :chat_message }, + ).as_json content[:type] = :refresh MessageBus.publish("/chat/#{chat_channel.id}", content.as_json, permissions(chat_channel)) end @@ -40,9 +56,13 @@ def self.publish_reaction!(chat_channel, chat_message, action, user, emoji) user: BasicUserSerializer.new(user, root: false).as_json, emoji: emoji, type: :reaction, - chat_message_id: chat_message.id + chat_message_id: chat_message.id, } - MessageBus.publish("/chat/message-reactions/#{chat_message.id}", content.as_json, permissions(chat_channel)) + MessageBus.publish( + "/chat/message-reactions/#{chat_message.id}", + content.as_json, + permissions(chat_channel), + ) MessageBus.publish("/chat/#{chat_channel.id}", content.as_json, permissions(chat_channel)) end @@ -54,7 +74,7 @@ def self.publish_delete!(chat_channel, chat_message) MessageBus.publish( "/chat/#{chat_channel.id}", { type: "delete", deleted_id: chat_message.id, deleted_at: chat_message.deleted_at }, - permissions(chat_channel) + permissions(chat_channel), ) end @@ -62,12 +82,16 @@ def self.publish_bulk_delete!(chat_channel, deleted_message_ids) MessageBus.publish( "/chat/#{chat_channel.id}", { typ: "bulk_delete", deleted_ids: deleted_message_ids, deleted_at: Time.zone.now }, - permissions(chat_channel) + permissions(chat_channel), ) end def self.publish_restore!(chat_channel, chat_message) - content = ChatMessageSerializer.new(chat_message, { scope: anonymous_guardian, root: :chat_message }).as_json + content = + ChatMessageSerializer.new( + chat_message, + { scope: anonymous_guardian, root: :chat_message }, + ).as_json content[:type] = :restore MessageBus.publish("/chat/#{chat_channel.id}", content.as_json, permissions(chat_channel)) end @@ -79,20 +103,16 @@ def self.publish_flag!(chat_message, user, reviewable) { type: "self_flagged", user_flag_status: ReviewableScore.statuses[:pending], - chat_message_id: chat_message.id + chat_message_id: chat_message.id, }.as_json, - user_ids: [user.id] + user_ids: [user.id], ) # Publish flag with link to reviewable to staff MessageBus.publish( "/chat/#{chat_message.chat_channel_id}", - { - type: "flag", - chat_message_id: chat_message.id, - reviewable_id: reviewable.id - }.as_json, - group_ids: [Group::AUTO_GROUPS[:staff]] + { type: "flag", chat_message_id: chat_message.id, reviewable_id: reviewable.id }.as_json, + group_ids: [Group::AUTO_GROUPS[:staff]], ) end @@ -100,59 +120,82 @@ def self.publish_user_tracking_state(user, chat_channel_id, chat_message_id) MessageBus.publish( "/chat/user-tracking-state/#{user.id}", { chat_channel_id: chat_channel_id, chat_message_id: chat_message_id.to_i }.as_json, - user_ids: [user.id] + user_ids: [user.id], ) end def self.publish_new_mention(user_id, chat_channel_id, chat_message_id) - MessageBus.publish("/chat/#{chat_channel_id}/new-mentions", { message_id: chat_message_id }.as_json, user_ids: [user_id]) + MessageBus.publish( + "/chat/#{chat_channel_id}/new-mentions", + { message_id: chat_message_id }.as_json, + user_ids: [user_id], + ) end def self.publish_new_channel(chat_channel, users) users.each do |user| - serialized_channel = ChatChannelSerializer.new( - chat_channel, - scope: Guardian.new(user), # We need a guardian here for direct messages - root: :chat_channel - ).as_json + serialized_channel = + ChatChannelSerializer.new( + chat_channel, + scope: Guardian.new(user), # We need a guardian here for direct messages + root: :chat_channel, + ).as_json MessageBus.publish("/chat/new-channel", serialized_channel, user_ids: [user.id]) end end - def self.publish_inaccessible_mentions(user_id, chat_message, cannot_chat_users, without_membership) - MessageBus.publish("/chat/#{chat_message.chat_channel_id}", { + def self.publish_inaccessible_mentions( + user_id, + chat_message, + cannot_chat_users, + without_membership + ) + MessageBus.publish( + "/chat/#{chat_message.chat_channel_id}", + { type: :mention_warning, chat_message_id: chat_message.id, - cannot_see: ActiveModel::ArraySerializer.new(cannot_chat_users, each_serializer: BasicUserSerializer).as_json, - without_membership: ActiveModel::ArraySerializer.new(without_membership, each_serializer: BasicUserSerializer).as_json, + cannot_see: + ActiveModel::ArraySerializer.new( + cannot_chat_users, + each_serializer: BasicUserSerializer, + ).as_json, + without_membership: + ActiveModel::ArraySerializer.new( + without_membership, + each_serializer: BasicUserSerializer, + ).as_json, }, - user_ids: [user_id] + user_ids: [user_id], ) end def self.publish_chat_channel_edit(chat_channel, acting_user) - MessageBus.publish("/chat/channel-edits", { + MessageBus.publish( + "/chat/channel-edits", + { chat_channel_id: chat_channel.id, name: chat_channel.title(acting_user), description: chat_channel.description, }, - permissions(chat_channel) + permissions(chat_channel), ) end def self.publish_channel_status(chat_channel) MessageBus.publish( "/chat/channel-status", - { - chat_channel_id: chat_channel.id, - status: chat_channel.status - }, - permissions(chat_channel) + { chat_channel_id: chat_channel.id, status: chat_channel.status }, + permissions(chat_channel), ) end def self.publish_archive_status( - chat_channel, archive_status:, archived_messages:, archive_topic_id:, total_messages: + chat_channel, + archive_status:, + archived_messages:, + archive_topic_id:, + total_messages: ) MessageBus.publish( "/chat/channel-archive-status", @@ -162,9 +205,9 @@ def self.publish_archive_status( archive_completed: archive_status == :success, archived_messages: archived_messages, total_messages: total_messages, - archive_topic_id: archive_topic_id + archive_topic_id: archive_topic_id, }, - permissions(chat_channel) + permissions(chat_channel), ) end diff --git a/db/migrate/20210225230057_create_chat_tables.rb b/db/migrate/20210225230057_create_chat_tables.rb index 059275896..21844f7ea 100644 --- a/db/migrate/20210225230057_create_chat_tables.rb +++ b/db/migrate/20210225230057_create_chat_tables.rb @@ -22,6 +22,6 @@ def change t.text :message end - add_index :topic_chat_messages, [:topic_id, :created_at] + add_index :topic_chat_messages, %i[topic_id created_at] end end diff --git a/db/migrate/20210706214013_rename_topic_chats_to_chat_channels.rb b/db/migrate/20210706214013_rename_topic_chats_to_chat_channels.rb index a7d43489f..134417d38 100644 --- a/db/migrate/20210706214013_rename_topic_chats_to_chat_channels.rb +++ b/db/migrate/20210706214013_rename_topic_chats_to_chat_channels.rb @@ -15,7 +15,7 @@ def up change_column :chat_channels, :chatable_id, :integer, unique: false add_column :chat_channels, :chatable_type, :string change_column_null :chat_channels, :chatable_type, false - add_index :chat_channels, [:chatable_id, :chatable_type] + add_index :chat_channels, %i[chatable_id chatable_type] # topic_chat_messages table changes rename_table :topic_chat_messages, :chat_messages diff --git a/db/migrate/20210730134847_create_user_chat_channel_last_read.rb b/db/migrate/20210730134847_create_user_chat_channel_last_read.rb index 44a5021ac..a0b730468 100644 --- a/db/migrate/20210730134847_create_user_chat_channel_last_read.rb +++ b/db/migrate/20210730134847_create_user_chat_channel_last_read.rb @@ -8,6 +8,9 @@ def change t.integer :user_id, null: false end - add_index :user_chat_channel_last_reads, [:chat_channel_id, :user_id], unique: true, name: "user_chat_channel_reads_index" + add_index :user_chat_channel_last_reads, + %i[chat_channel_id user_id], + unique: true, + name: "user_chat_channel_reads_index" end end diff --git a/db/migrate/20210812145801_create_direct_message_tables.rb b/db/migrate/20210812145801_create_direct_message_tables.rb index d976f3280..3b231e078 100644 --- a/db/migrate/20210812145801_create_direct_message_tables.rb +++ b/db/migrate/20210812145801_create_direct_message_tables.rb @@ -13,8 +13,8 @@ def change end add_index :direct_message_users, - [:direct_message_channel_id, :user_id], - unique: true, - name: "direct_message_users_index" + %i[direct_message_channel_id user_id], + unique: true, + name: "direct_message_users_index" end end diff --git a/db/migrate/20210819202912_create_incoming_chat_webhooks.rb b/db/migrate/20210819202912_create_incoming_chat_webhooks.rb index 5d0d65139..23cc115b7 100644 --- a/db/migrate/20210819202912_create_incoming_chat_webhooks.rb +++ b/db/migrate/20210819202912_create_incoming_chat_webhooks.rb @@ -12,6 +12,6 @@ def change t.timestamps end - add_index :incoming_chat_webhooks, [:key, :chat_channel_id] + add_index :incoming_chat_webhooks, %i[key chat_channel_id] end end diff --git a/db/migrate/20210823160357_create_chat_webhook_events.rb b/db/migrate/20210823160357_create_chat_webhook_events.rb index 7cc3b1740..4e9576775 100644 --- a/db/migrate/20210823160357_create_chat_webhook_events.rb +++ b/db/migrate/20210823160357_create_chat_webhook_events.rb @@ -8,8 +8,8 @@ def change end add_index :chat_webhook_events, - [:chat_message_id, :incoming_chat_webhook_id], - unique: true, - name: "chat_webhook_events_index" + %i[chat_message_id incoming_chat_webhook_id], + unique: true, + name: "chat_webhook_events_index" end end diff --git a/db/migrate/20210901130308_create_user_chat_channel_membership.rb b/db/migrate/20210901130308_create_user_chat_channel_membership.rb index b083cdc11..81eea0a67 100644 --- a/db/migrate/20210901130308_create_user_chat_channel_membership.rb +++ b/db/migrate/20210901130308_create_user_chat_channel_membership.rb @@ -13,21 +13,18 @@ def change end add_index :user_chat_channel_memberships, - [ - :user_id, - :chat_channel_id, - :desktop_notification_level, - :mobile_notification_level, - :following - ], - name: "user_chat_channel_memberships_index" + %i[ + user_id + chat_channel_id + desktop_notification_level + mobile_notification_level + following + ], + name: "user_chat_channel_memberships_index" add_index :user_chat_channel_memberships, - [ - :user_id, - :chat_channel_id, - ], - unique: true, - name: "user_chat_channel_unique_memberships" + %i[user_id chat_channel_id], + unique: true, + name: "user_chat_channel_unique_memberships" end end diff --git a/db/migrate/20211022151713_create_chat_message_post_connections.rb b/db/migrate/20211022151713_create_chat_message_post_connections.rb index f9ac0cbdf..dab116d3c 100644 --- a/db/migrate/20211022151713_create_chat_message_post_connections.rb +++ b/db/migrate/20211022151713_create_chat_message_post_connections.rb @@ -8,8 +8,8 @@ def change end add_index :chat_message_post_connections, - [:post_id, :chat_message_id], - unique: true, - name: "chat_message_post_connections_index" + %i[post_id chat_message_id], + unique: true, + name: "chat_message_post_connections_index" end end diff --git a/db/migrate/20211129171229_create_chat_uploads.rb b/db/migrate/20211129171229_create_chat_uploads.rb index bd6d73d9d..7eb9d5668 100644 --- a/db/migrate/20211129171229_create_chat_uploads.rb +++ b/db/migrate/20211129171229_create_chat_uploads.rb @@ -8,6 +8,6 @@ def change t.timestamps end - add_index :chat_uploads, [:chat_message_id, :upload_id], unique: true + add_index :chat_uploads, %i[chat_message_id upload_id], unique: true end end diff --git a/db/migrate/20211201171813_create_chat_reactions.rb b/db/migrate/20211201171813_create_chat_reactions.rb index d5459753c..377849e50 100644 --- a/db/migrate/20211201171813_create_chat_reactions.rb +++ b/db/migrate/20211201171813_create_chat_reactions.rb @@ -9,8 +9,8 @@ def change end add_index :chat_message_reactions, - [:chat_message_id, :user_id, :emoji], - unique: true, - name: :chat_message_reactions_index + %i[chat_message_id user_id emoji], + unique: true, + name: :chat_message_reactions_index end end diff --git a/db/migrate/20211210191830_create_chat_mentions.rb b/db/migrate/20211210191830_create_chat_mentions.rb index 99a3689b0..26f42042d 100644 --- a/db/migrate/20211210191830_create_chat_mentions.rb +++ b/db/migrate/20211210191830_create_chat_mentions.rb @@ -8,6 +8,9 @@ def change t.timestamps end - add_index :chat_mentions, [:chat_message_id, :user_id, :notification_id], unique: true, name: "chat_mentions_index" + add_index :chat_mentions, + %i[chat_message_id user_id notification_id], + unique: true, + name: "chat_mentions_index" end end diff --git a/db/migrate/20220309174820_add_last_message_created_at_to_chat_channels.rb b/db/migrate/20220309174820_add_last_message_created_at_to_chat_channels.rb index 29957e552..5f07d4cf8 100644 --- a/db/migrate/20220309174820_add_last_message_created_at_to_chat_channels.rb +++ b/db/migrate/20220309174820_add_last_message_created_at_to_chat_channels.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class AddLastMessageCreatedAtToChatChannels < ActiveRecord::Migration[6.1] def change - add_column :chat_channels, :last_message_sent_at, :datetime, default: -> { 'CURRENT_TIMESTAMP' } + add_column :chat_channels, :last_message_sent_at, :datetime, default: -> { "CURRENT_TIMESTAMP" } change_column_null :chat_channels, :last_message_sent_at, false end end diff --git a/db/migrate/20220328142120_create_user_chat_message_statuses.rb b/db/migrate/20220328142120_create_user_chat_message_statuses.rb index 353632bab..a1012b44a 100644 --- a/db/migrate/20220328142120_create_user_chat_message_statuses.rb +++ b/db/migrate/20220328142120_create_user_chat_message_statuses.rb @@ -10,7 +10,9 @@ def change t.timestamps end - add_index :chat_message_email_statuses, [:user_id, :chat_message_id], name: "chat_message_email_status_user_message_index" + add_index :chat_message_email_statuses, + %i[user_id chat_message_id], + name: "chat_message_email_status_user_message_index" add_index :chat_message_email_statuses, :status add_column :user_options, :chat_email_frequency, :integer, default: 1, null: false diff --git a/db/post_migrate/20220321235638_drop_chat_message_post_connections_table.rb b/db/post_migrate/20220321235638_drop_chat_message_post_connections_table.rb index de68e377d..ca6d3b795 100644 --- a/db/post_migrate/20220321235638_drop_chat_message_post_connections_table.rb +++ b/db/post_migrate/20220321235638_drop_chat_message_post_connections_table.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'migration/table_dropper' +require "migration/table_dropper" class DropChatMessagePostConnectionsTable < ActiveRecord::Migration[6.1] def up diff --git a/db/post_migrate/20220504080457_drop_old_chat_message_post_id_action_code_columns.rb b/db/post_migrate/20220504080457_drop_old_chat_message_post_id_action_code_columns.rb index 696c11018..6ca1c406b 100644 --- a/db/post_migrate/20220504080457_drop_old_chat_message_post_id_action_code_columns.rb +++ b/db/post_migrate/20220504080457_drop_old_chat_message_post_id_action_code_columns.rb @@ -1,17 +1,10 @@ # frozen_string_literal: true class DropOldChatMessagePostIdActionCodeColumns < ActiveRecord::Migration[7.0] - DROPPED_COLUMNS ||= { - chat_messages: %i{ - post_id - action_code - } - } + DROPPED_COLUMNS ||= { chat_messages: %i[post_id action_code] } def up - DROPPED_COLUMNS.each do |table, columns| - Migration::ColumnDropper.execute_drop(table, columns) - end + DROPPED_COLUMNS.each { |table, columns| Migration::ColumnDropper.execute_drop(table, columns) } end def down diff --git a/db/post_migrate/20220516142658_remove_email_statuses_table.rb b/db/post_migrate/20220516142658_remove_email_statuses_table.rb index 6f40b4ece..4daf38ae0 100644 --- a/db/post_migrate/20220516142658_remove_email_statuses_table.rb +++ b/db/post_migrate/20220516142658_remove_email_statuses_table.rb @@ -3,7 +3,7 @@ class RemoveEmailStatusesTable < ActiveRecord::Migration[7.0] def up remove_index :chat_message_email_statuses, :status - remove_index :chat_message_email_statuses, [:user_id, :chat_message_id] + remove_index :chat_message_email_statuses, %i[user_id chat_message_id] Migration::TableDropper.execute_drop("chat_message_email_statuses") end diff --git a/db/post_migrate/20220531105951_drop_user_chat_channel_last_reads.rb b/db/post_migrate/20220531105951_drop_user_chat_channel_last_reads.rb index 0bbee87be..b746266d8 100644 --- a/db/post_migrate/20220531105951_drop_user_chat_channel_last_reads.rb +++ b/db/post_migrate/20220531105951_drop_user_chat_channel_last_reads.rb @@ -1,16 +1,14 @@ # frozen_string_literal: true -require 'migration/table_dropper' +require "migration/table_dropper" # usage has been dropped in https://github.com/discourse/discourse-chat/commit/1c110b71b28411dc7ac3ab9e3950e0bbf38d7970 # but table never got dropped class DropUserChatChannelLastReads < ActiveRecord::Migration[7.0] - DROPPED_TABLES ||= %i{ user_chat_channel_last_reads } + DROPPED_TABLES ||= %i[user_chat_channel_last_reads] def up - DROPPED_TABLES.each do |table| - Migration::TableDropper.execute_drop(table) - end + DROPPED_TABLES.each { |table| Migration::TableDropper.execute_drop(table) } end def down diff --git a/db/post_migrate/20220630074200_drop_chat_isolated_from_user_options.rb b/db/post_migrate/20220630074200_drop_chat_isolated_from_user_options.rb index 7c1866eff..0a4114941 100644 --- a/db/post_migrate/20220630074200_drop_chat_isolated_from_user_options.rb +++ b/db/post_migrate/20220630074200_drop_chat_isolated_from_user_options.rb @@ -1,14 +1,10 @@ # frozen_string_literal: true class DropChatIsolatedFromUserOptions < ActiveRecord::Migration[7.0] - DROPPED_COLUMNS ||= { - user_options: %i{ chat_isolated } - } + DROPPED_COLUMNS ||= { user_options: %i[chat_isolated] } def up - DROPPED_COLUMNS.each do |table, columns| - Migration::ColumnDropper.execute_drop(table, columns) - end + DROPPED_COLUMNS.each { |table, columns| Migration::ColumnDropper.execute_drop(table, columns) } end def down diff --git a/db/post_migrate/20220701195731_convert_chatable_topics_to_categories.rb b/db/post_migrate/20220701195731_convert_chatable_topics_to_categories.rb index 6b78149a9..05e61f5f8 100644 --- a/db/post_migrate/20220701195731_convert_chatable_topics_to_categories.rb +++ b/db/post_migrate/20220701195731_convert_chatable_topics_to_categories.rb @@ -16,9 +16,9 @@ def up # soft delete all posts small actions DB.exec( "UPDATE posts SET deleted_at = :deleted_at, deleted_by_id = :deleted_by_id WHERE action_code IN (:action_codes)", - action_codes: ['chat.enabled', 'chat.disabled'], + action_codes: %w[chat.enabled chat.disabled], deleted_at: Time.zone.now, - deleted_by_id: Discourse::SYSTEM_USER_ID + deleted_by_id: Discourse::SYSTEM_USER_ID, ) # removes all chat custom fields diff --git a/lib/chat_channel_archive_service.rb b/lib/chat_channel_archive_service.rb index bc513c27c..8f64bb6bf 100644 --- a/lib/chat_channel_archive_service.rb +++ b/lib/chat_channel_archive_service.rb @@ -23,15 +23,16 @@ def self.begin_archive_process(chat_channel:, acting_user:, topic_params:) ChatChannelArchive.transaction do chat_channel.read_only!(acting_user) - archive = ChatChannelArchive.create!( - chat_channel: chat_channel, - archived_by: acting_user, - total_messages: chat_channel.chat_messages.count, - destination_topic_id: topic_params[:topic_id], - destination_topic_title: topic_params[:topic_title], - destination_category_id: topic_params[:category_id], - destination_tags: topic_params[:tags], - ) + archive = + ChatChannelArchive.create!( + chat_channel: chat_channel, + archived_by: acting_user, + total_messages: chat_channel.chat_messages.count, + destination_topic_id: topic_params[:topic_id], + destination_topic_title: topic_params[:topic_title], + destination_category_id: topic_params[:category_id], + destination_tags: topic_params[:tags], + ) Jobs.enqueue(:chat_channel_archive, chat_channel_archive_id: archive.id) archive @@ -40,7 +41,10 @@ def self.begin_archive_process(chat_channel:, acting_user:, topic_params:) def self.retry_archive_process(chat_channel:) return if !chat_channel.chat_channel_archive&.failed? - Jobs.enqueue(:chat_channel_archive, chat_channel_archive_id: chat_channel.chat_channel_archive.id) + Jobs.enqueue( + :chat_channel_archive, + chat_channel_archive_id: chat_channel.chat_channel_archive.id, + ) end attr_reader :chat_channel_archive, :chat_channel, :chat_channel_title @@ -57,7 +61,9 @@ def execute begin ensure_destination_topic_exists! - Rails.logger.info("Creating posts from message batches for #{chat_channel_title} archive, #{chat_channel_archive.total_messages} messages to archive (#{chat_channel_archive.total_messages / ARCHIVED_MESSAGES_PER_POST} posts).") + Rails.logger.info( + "Creating posts from message batches for #{chat_channel_title} archive, #{chat_channel_archive.total_messages} messages to archive (#{chat_channel_archive.total_messages / ARCHIVED_MESSAGES_PER_POST} posts).", + ) # a batch should be idempotent, either the post is created and the # messages are deleted or we roll back the whole thing. @@ -69,20 +75,21 @@ def execute # another future improvement is to send a MessageBus message for each # completed batch, so the UI can receive updates and show a progress # bar or something similar - chat_channel.chat_messages.find_in_batches( - batch_size: ARCHIVED_MESSAGES_PER_POST - ) do |chat_messages| - create_post( - ChatTranscriptService.new( - chat_channel, - chat_channel_archive.archived_by, - messages_or_ids: chat_messages, - opts: { no_link: true, include_reactions: true } - ).generate_markdown - ) do - delete_message_batch(chat_messages.map(&:id)) + chat_channel + .chat_messages + .find_in_batches(batch_size: ARCHIVED_MESSAGES_PER_POST) do |chat_messages| + create_post( + ChatTranscriptService.new( + chat_channel, + chat_channel_archive.archived_by, + messages_or_ids: chat_messages, + opts: { + no_link: true, + include_reactions: true, + }, + ).generate_markdown, + ) { delete_message_batch(chat_messages.map(&:id)) } end - end kick_all_users complete_archive @@ -97,23 +104,20 @@ def execute def create_post(raw) pc = nil Post.transaction do - pc = PostCreator.new( - Discourse.system_user, - raw: raw, - - # we must skip these because the posts are created in a big transaction, - # we do them all at the end instead - skip_jobs: true, - - # we do not want to be sending out notifications etc. from this - # automatic background process - import_mode: true, - - # don't want to be stopped by watched word or post length validations - skip_validations: true, - - topic_id: chat_channel_archive.destination_topic_id - ) + pc = + PostCreator.new( + Discourse.system_user, + raw: raw, + # we must skip these because the posts are created in a big transaction, + # we do them all at the end instead + skip_jobs: true, + # we do not want to be sending out notifications etc. from this + # automatic background process + import_mode: true, + # don't want to be stopped by watched word or post length validations + skip_validations: true, + topic_id: chat_channel_archive.destination_topic_id, + ) pc.create @@ -127,16 +131,17 @@ def ensure_destination_topic_exists! if !chat_channel_archive.destination_topic.present? Rails.logger.info("Creating topic for #{chat_channel_title} archive.") Topic.transaction do - topic_creator = TopicCreator.new( - Discourse.system_user, - Guardian.new(chat_channel_archive.archived_by), - { - title: chat_channel_archive.destination_topic_title, - category: chat_channel_archive.destination_category_id, - tags: chat_channel_archive.destination_tags, - import_mode: true - } - ) + topic_creator = + TopicCreator.new( + Discourse.system_user, + Guardian.new(chat_channel_archive.archived_by), + { + title: chat_channel_archive.destination_topic_title, + category: chat_channel_archive.destination_category_id, + tags: chat_channel_archive.destination_tags, + import_mode: true, + }, + ) chat_channel_archive.update!(destination_topic: topic_creator.create) end @@ -146,8 +151,8 @@ def ensure_destination_topic_exists! I18n.t( "chat.channel.archive.first_post_raw", channel_name: chat_channel_title, - channel_url: chat_channel.url - ) + channel_url: chat_channel.url, + ), ) else Rails.logger.info("Topic already exists for #{chat_channel_title} archive.") @@ -173,15 +178,17 @@ def delete_message_batch(message_ids) ChatMessage.transaction do ChatMessage.where(id: message_ids).update_all( deleted_at: DateTime.now, - deleted_by_id: chat_channel_archive.archived_by.id + deleted_by_id: chat_channel_archive.archived_by.id, ) chat_channel_archive.update!( - archived_messages: chat_channel_archive.archived_messages + message_ids.length + archived_messages: chat_channel_archive.archived_messages + message_ids.length, ) end - Rails.logger.info("Archived #{chat_channel_archive.archived_messages} messages for #{chat_channel_title} archive.") + Rails.logger.info( + "Archived #{chat_channel_archive.archived_messages} messages for #{chat_channel_title} archive.", + ) end def complete_archive @@ -194,7 +201,7 @@ def notify_archiver(result, error: nil) base_translation_params = { channel_name: chat_channel_title, topic_title: chat_channel_archive.destination_topic.title, - topic_url: chat_channel_archive.destination_topic.url + topic_url: chat_channel_archive.destination_topic.url, } if result == :failed @@ -203,20 +210,25 @@ def notify_archiver(result, error: nil) message: "Error when archiving chat channel #{chat_channel_title}.", env: { chat_channel_id: chat_channel.id, - chat_channel_name: chat_channel_title - } - ) - error_translation_params = base_translation_params.merge( - channel_url: chat_channel.url, - messages_archived: chat_channel_archive.archived_messages + chat_channel_name: chat_channel_title, + }, ) + error_translation_params = + base_translation_params.merge( + channel_url: chat_channel.url, + messages_archived: chat_channel_archive.archived_messages, + ) chat_channel_archive.update(archive_error: error.message) SystemMessage.create_from_system_user( - chat_channel_archive.archived_by, :chat_channel_archive_failed, error_translation_params + chat_channel_archive.archived_by, + :chat_channel_archive_failed, + error_translation_params, ) else SystemMessage.create_from_system_user( - chat_channel_archive.archived_by, :chat_channel_archive_complete, base_translation_params + chat_channel_archive.archived_by, + :chat_channel_archive_complete, + base_translation_params, ) end @@ -225,13 +237,14 @@ def notify_archiver(result, error: nil) archive_status: result, archived_messages: chat_channel_archive.archived_messages, archive_topic_id: chat_channel_archive.destination_topic_id, - total_messages: chat_channel_archive.total_messages + total_messages: chat_channel_archive.total_messages, ) end def kick_all_users UserChatChannelMembership.where(chat_channel: chat_channel).update_all( - following: false, last_read_message_id: chat_channel.chat_messages.last&.id + following: false, + last_read_message_id: chat_channel.chat_messages.last&.id, ) end end diff --git a/lib/chat_channel_fetcher.rb b/lib/chat_channel_fetcher.rb index 679f51c44..681bacb00 100644 --- a/lib/chat_channel_fetcher.rb +++ b/lib/chat_channel_fetcher.rb @@ -6,35 +6,43 @@ module DiscourseChat::ChatChannelFetcher def self.structured(guardian) memberships = UserChatChannelMembership.where(user_id: guardian.user.id) { - public_channels: secured_public_channels( - guardian, - memberships, - status: :open, - following: true - ), - direct_message_channels: secured_direct_message_channels( - guardian.user.id, - memberships, - guardian - ), + public_channels: + secured_public_channels(guardian, memberships, status: :open, following: true), + direct_message_channels: + secured_direct_message_channels(guardian.user.id, memberships, guardian), } end def self.all_secured_channel_ids(guardian) allowed_channel_ids_sql = <<~SQL -- secured category chat channels - #{ChatChannel.select(:id).joins( - "INNER JOIN categories ON categories.id = chat_channels.chatable_id AND chat_channels.chatable_type = 'Category'" - ).where("categories.id IN (:allowed_category_ids)", allowed_category_ids: guardian.allowed_category_ids).to_sql} + #{ + ChatChannel + .select(:id) + .joins( + "INNER JOIN categories ON categories.id = chat_channels.chatable_id AND chat_channels.chatable_type = 'Category'", + ) + .where( + "categories.id IN (:allowed_category_ids)", + allowed_category_ids: guardian.allowed_category_ids, + ) + .to_sql + } UNION -- secured direct message chat channels - #{ChatChannel.select(:id).joins( - "INNER JOIN direct_message_channels ON direct_message_channels.id = chat_channels.chatable_id + #{ + ChatChannel + .select(:id) + .joins( + "INNER JOIN direct_message_channels ON direct_message_channels.id = chat_channels.chatable_id AND chat_channels.chatable_type = 'DirectMessageChannel' - INNER JOIN direct_message_users ON direct_message_users.direct_message_channel_id = direct_message_channels.id" - ).where("direct_message_users.user_id = :user_id", user_id: guardian.user.id).to_sql} + INNER JOIN direct_message_users ON direct_message_users.direct_message_channel_id = direct_message_channels.id", + ) + .where("direct_message_users.user_id = :user_id", user_id: guardian.user.id) + .to_sql + } SQL DB.query_single(<<~SQL, user_id: guardian.user.id) @@ -48,26 +56,33 @@ def self.all_secured_channel_ids(guardian) end def self.secured_public_channels(guardian, memberships, options = { following: true }) - channels = ChatChannel.includes(:chatable, :chat_channel_archive) - .joins("LEFT JOIN categories ON categories.id = chat_channels.chatable_id AND chat_channels.chatable_type = 'Category'") - .where(chatable_type: ChatChannel.public_channel_chatable_types) + channels = + ChatChannel + .includes(:chatable, :chat_channel_archive) + .joins( + "LEFT JOIN categories ON categories.id = chat_channels.chatable_id AND chat_channels.chatable_type = 'Category'", + ) + .where(chatable_type: ChatChannel.public_channel_chatable_types) - if options[:status].present? - channels = channels.where(status: options[:status]) - end + channels = channels.where(status: options[:status]) if options[:status].present? if options[:filter].present? - channels = channels - .where(<<~SQL, filter: "%#{options[:filter].downcase}%") + channels = + channels.where(<<~SQL, filter: "%#{options[:filter].downcase}%").order( chat_channels.name ILIKE :filter OR categories.name ILIKE :filter SQL - .order('chat_channels.name ASC, categories.name ASC') + "chat_channels.name ASC, categories.name ASC", + ) end if options[:following].present? - channels = channels - .joins(:user_chat_channel_memberships) - .where(user_chat_channel_memberships: { user_id: guardian.user.id, following: true }) + channels = + channels.joins(:user_chat_channel_memberships).where( + user_chat_channel_memberships: { + user_id: guardian.user.id, + following: true, + }, + ) end options[:limit] = (options[:limit] || MAX_RESULTS).to_i.clamp(1, MAX_RESULTS) @@ -81,14 +96,18 @@ def self.secured_public_channels(guardian, memberships, options = { following: t def self.preload_custom_fields_for(channels) preload_fields = Category.instance_variable_get(:@custom_field_types).keys - Category.preload_custom_fields(channels.select { |c| c.chatable_type == 'Category' }.map(&:chatable), preload_fields) + Category.preload_custom_fields( + channels.select { |c| c.chatable_type == "Category" }.map(&:chatable), + preload_fields, + ) end def self.filter_public_channels(channels, memberships, guardian) - mention_notifications = Notification.unread.where( - user_id: guardian.user.id, - notification_type: Notification.types[:chat_mention], - ) + mention_notifications = + Notification.unread.where( + user_id: guardian.user.id, + notification_type: Notification.types[:chat_mention], + ) mention_notification_data = mention_notifications.map { |m| JSON.parse(m.data) } unread_counts_per_channel = unread_counts(channels, guardian.user.id) @@ -98,30 +117,35 @@ def self.filter_public_channels(channels, memberships, guardian) membership = memberships.find { |m| m.chat_channel_id == channel.id } if membership - channel = decorate_channel_from_membership( - guardian.user.id, - channel, - membership, - mention_notification_data - ) - - if !channel.muted - channel.unread_count = unread_counts_per_channel[channel.id] - end + channel = + decorate_channel_from_membership( + guardian.user.id, + channel, + membership, + mention_notification_data, + ) + + channel.unread_count = unread_counts_per_channel[channel.id] if !channel.muted end channel end end - def self.decorate_channel_from_membership(user_id, channel, membership, mention_notification_data = nil) + def self.decorate_channel_from_membership( + user_id, + channel, + membership, + mention_notification_data = nil + ) channel.last_read_message_id = membership.last_read_message_id channel.muted = membership.muted if mention_notification_data - channel.unread_mentions = mention_notification_data.count { |data| - data["chat_channel_id"] == channel.id && - data["chat_message_id"] > (membership.last_read_message_id || 0) - } + channel.unread_mentions = + mention_notification_data.count do |data| + data["chat_channel_id"] == channel.id && + data["chat_message_id"] > (membership.last_read_message_id || 0) + end end channel.following = membership.following channel.desktop_notification_level = membership.desktop_notification_level @@ -130,15 +154,18 @@ def self.decorate_channel_from_membership(user_id, channel, membership, mention_ end def self.secured_direct_message_channels(user_id, memberships, guardian) - channels = ChatChannel - .includes(chatable: [{ direct_message_users: :user }, :users ]) - .joins(:user_chat_channel_memberships) - .where(user_chat_channel_memberships: { user_id: user_id, following: true }) - .where(chatable_type: "DirectMessageChannel") - .order(last_message_sent_at: :desc) - .to_a - - preload_fields = User.allowed_user_custom_fields(guardian) + UserField.all.pluck(:id).map { |fid| "#{User::USER_FIELD_PREFIX}#{fid}" } + channels = + ChatChannel + .includes(chatable: [{ direct_message_users: :user }, :users]) + .joins(:user_chat_channel_memberships) + .where(user_chat_channel_memberships: { user_id: user_id, following: true }) + .where(chatable_type: "DirectMessageChannel") + .order(last_message_sent_at: :desc) + .to_a + + preload_fields = + User.allowed_user_custom_fields(guardian) + + UserField.all.pluck(:id).map { |fid| "#{User::USER_FIELD_PREFIX}#{fid}" } User.preload_custom_fields(channels.map { |c| c.chatable.users }.flatten, preload_fields) unread_counts_per_channel = unread_counts(channels, user_id) @@ -146,11 +173,12 @@ def self.secured_direct_message_channels(user_id, memberships, guardian) channels.filter_map do |channel| next if !guardian.can_see_chat_channel?(channel) - channel = decorate_channel_from_membership( - user_id, - channel, - memberships.find { |m| m.user_id == user_id && m.chat_channel_id == channel.id } - ) + channel = + decorate_channel_from_membership( + user_id, + channel, + memberships.find { |m| m.user_id == user_id && m.chat_channel_id == channel.id }, + ) # direct message channels cannot be muted, so we always need the unread count channel.unread_count = unread_counts_per_channel[channel.id] @@ -182,9 +210,10 @@ def self.find_with_access_check(channel_id_or_name, guardian) rescue ArgumentError end - base_channel_relation = ChatChannel - .includes(:chatable) - .joins("LEFT JOIN categories ON categories.id = chat_channels.chatable_id AND chat_channels.chatable_type = 'Category'") + base_channel_relation = + ChatChannel.includes(:chatable).joins( + "LEFT JOIN categories ON categories.id = chat_channels.chatable_id AND chat_channels.chatable_type = 'Category'", + ) if guardian.user.staff? base_channel_relation = base_channel_relation.includes(:chat_channel_archive) @@ -193,7 +222,11 @@ def self.find_with_access_check(channel_id_or_name, guardian) if channel_id_or_name.is_a? Integer chat_channel = base_channel_relation.find_by(id: channel_id_or_name) else - chat_channel = base_channel_relation.find_by("LOWER(categories.name) = :name OR LOWER(chat_channels.name) = :name", name: channel_id_or_name.downcase) + chat_channel = + base_channel_relation.find_by( + "LOWER(categories.name) = :name OR LOWER(chat_channels.name) = :name", + name: channel_id_or_name.downcase, + ) end raise Discourse::NotFound if chat_channel.blank? diff --git a/lib/chat_mailer.rb b/lib/chat_mailer.rb index 843295f18..3d4e64a72 100644 --- a/lib/chat_mailer.rb +++ b/lib/chat_mailer.rb @@ -7,17 +7,21 @@ def self.send_unread_mentions_summary users_with_unprocessed_unread_mentions.find_each do |user| # user#memberships_with_unread_messages is a nested array that looks like [[membership_id, unread_message_id]] # Find the max unread id per membership. - membership_and_max_unread_mention_ids = user.memberships_with_unread_messages - .group_by { |memberships| memberships[0] } - .transform_values do |membership_and_msg_ids| - membership_and_msg_ids.max_by { |membership, msg| msg } - end.values + membership_and_max_unread_mention_ids = + user + .memberships_with_unread_messages + .group_by { |memberships| memberships[0] } + .transform_values do |membership_and_msg_ids| + membership_and_msg_ids.max_by { |membership, msg| msg } + end + .values - Jobs.enqueue(:user_email, + Jobs.enqueue( + :user_email, type: "chat_summary", user_id: user.id, force_respect_seen_recently: true, - memberships_to_update_data: membership_and_max_unread_mention_ids + memberships_to_update_data: membership_and_max_unread_mention_ids, ) end end @@ -29,26 +33,24 @@ def self.users_with_unprocessed_unread_mentions allowed_group_ids = DiscourseChat.allowed_group_ids User - .select('users.id', 'ARRAY_AGG(ARRAY[uccm.id, c_msg.id]) AS memberships_with_unread_messages') + .select("users.id", "ARRAY_AGG(ARRAY[uccm.id, c_msg.id]) AS memberships_with_unread_messages") .joins(:user_option) .where(user_options: { chat_enabled: true, chat_email_frequency: when_away_frequency }) - .where('users.last_seen_at < ?', 15.minutes.ago) + .where("users.last_seen_at < ?", 15.minutes.ago) .joins(:groups) .where(groups: { id: allowed_group_ids }) - .joins('INNER JOIN user_chat_channel_memberships uccm ON uccm.user_id = users.id') - .joins('INNER JOIN chat_channels cc ON cc.id = uccm.chat_channel_id') - .joins('INNER JOIN chat_messages c_msg ON c_msg.chat_channel_id = uccm.chat_channel_id') - .joins('LEFT OUTER JOIN chat_mentions c_mentions ON c_mentions.chat_message_id = c_msg.id') + .joins("INNER JOIN user_chat_channel_memberships uccm ON uccm.user_id = users.id") + .joins("INNER JOIN chat_channels cc ON cc.id = uccm.chat_channel_id") + .joins("INNER JOIN chat_messages c_msg ON c_msg.chat_channel_id = uccm.chat_channel_id") + .joins("LEFT OUTER JOIN chat_mentions c_mentions ON c_mentions.chat_message_id = c_msg.id") .where(uccm: { following: true }) - .where('c_msg.deleted_at IS NULL AND c_msg.user_id <> users.id') - .where('c_msg.created_at > ?', 1.week.ago) - .where( - <<~SQL + .where("c_msg.deleted_at IS NULL AND c_msg.user_id <> users.id") + .where("c_msg.created_at > ?", 1.week.ago) + .where(<<~SQL) (c_mentions.user_id = uccm.user_id OR cc.chatable_type = 'DirectMessageChannel') AND (uccm.last_read_message_id IS NULL OR c_msg.id > uccm.last_read_message_id) AND (uccm.last_unread_mention_when_emailed_id IS NULL OR c_msg.id > uccm.last_unread_mention_when_emailed_id) SQL - ) - .group('users.id, uccm.user_id') + .group("users.id, uccm.user_id") end end diff --git a/lib/chat_message_bookmarkable.rb b/lib/chat_message_bookmarkable.rb index 83286e469..517699a5f 100644 --- a/lib/chat_message_bookmarkable.rb +++ b/lib/chat_message_bookmarkable.rb @@ -16,11 +16,12 @@ def self.preload_associations def self.list_query(user, guardian) accessible_channel_ids = DiscourseChat::ChatChannelFetcher.all_secured_channel_ids(guardian) return if accessible_channel_ids.empty? - user.bookmarks_of_type("ChatMessage") + user + .bookmarks_of_type("ChatMessage") .joins( "INNER JOIN chat_messages ON chat_messages.id = bookmarks.bookmarkable_id AND chat_messages.deleted_at IS NULL - AND bookmarks.bookmarkable_type = 'ChatMessage'" + AND bookmarks.bookmarkable_type = 'ChatMessage'", ) .where("chat_messages.chat_channel_id IN (?)", accessible_channel_ids) end @@ -30,19 +31,22 @@ def self.search_query(bookmarks, query, ts_query, &bookmarkable_search) end def self.validate_before_create(guardian, bookmarkable) - raise Discourse::InvalidAccess if bookmarkable.blank? || !guardian.can_see_chat_channel?(bookmarkable.chat_channel) + if bookmarkable.blank? || !guardian.can_see_chat_channel?(bookmarkable.chat_channel) + raise Discourse::InvalidAccess + end end def self.reminder_handler(bookmark) send_reminder_notification( bookmark, data: { - title: I18n.t( - "chat.bookmarkable.notification_title", - channel_name: bookmark.bookmarkable.chat_channel.title(bookmark.user) - ), - bookmarkable_url: bookmark.bookmarkable.url - } + title: + I18n.t( + "chat.bookmarkable.notification_title", + channel_name: bookmark.bookmarkable.chat_channel.title(bookmark.user), + ), + bookmarkable_url: bookmark.bookmarkable.url, + }, ) end diff --git a/lib/chat_message_creator.rb b/lib/chat_message_creator.rb index c72ae090e..2e7b83d97 100644 --- a/lib/chat_message_creator.rb +++ b/lib/chat_message_creator.rb @@ -8,7 +8,15 @@ def self.create(opts) instance end - def initialize(chat_channel:, in_reply_to_id: nil, user:, content:, staged_id: nil, incoming_chat_webhook: nil, upload_ids: nil) + def initialize( + chat_channel:, + in_reply_to_id: nil, + user:, + content:, + staged_id: nil, + incoming_chat_webhook: nil, + upload_ids: nil + ) @chat_channel = chat_channel @user = user @guardian = Guardian.new(user) @@ -19,12 +27,13 @@ def initialize(chat_channel:, in_reply_to_id: nil, user:, content:, staged_id: n @upload_ids = upload_ids || [] @error = nil - @chat_message = ChatMessage.new( - chat_channel: @chat_channel, - user_id: @user.id, - in_reply_to_id: @in_reply_to_id, - message: @content, - ) + @chat_message = + ChatMessage.new( + chat_channel: @chat_channel, + user_id: @user.id, + in_reply_to_id: @in_reply_to_id, + message: @content, + ) end def create @@ -41,7 +50,7 @@ def create Jobs.enqueue(:process_chat_message, { chat_message_id: @chat_message.id }) DiscourseChat::ChatNotifier.notify_new( chat_message: @chat_message, - timestamp: @chat_message.created_at + timestamp: @chat_message.created_at, ) rescue => error @error = error @@ -58,8 +67,8 @@ def validate_channel_status! return if @guardian.can_create_channel_message?(@chat_channel) raise StandardError.new( - I18n.t("chat.errors.channel_new_message_disallowed", status: @chat_channel.status_name) - ) + I18n.t("chat.errors.channel_new_message_disallowed", status: @chat_channel.status_name), + ) end def validate_message!(has_uploads:) @@ -73,7 +82,7 @@ def create_chat_webhook_event return if @incoming_chat_webhook.blank? ChatWebhookEvent.create( chat_message: @chat_message, - incoming_chat_webhook: @incoming_chat_webhook + incoming_chat_webhook: @incoming_chat_webhook, ) end diff --git a/lib/chat_message_rate_limiter.rb b/lib/chat_message_rate_limiter.rb index 3d96ca14c..76f2ca25a 100644 --- a/lib/chat_message_rate_limiter.rb +++ b/lib/chat_message_rate_limiter.rb @@ -13,9 +13,14 @@ def initialize(user) def run! return if @user.staff? - allowed_message_count = @user.trust_level == TrustLevel[0] ? - SiteSetting.chat_allowed_messages_for_trust_level_0 : - SiteSetting.chat_allowed_messages_for_other_trust_levels + allowed_message_count = + ( + if @user.trust_level == TrustLevel[0] + SiteSetting.chat_allowed_messages_for_trust_level_0 + else + SiteSetting.chat_allowed_messages_for_other_trust_levels + end + ) return if allowed_message_count.zero? @rate_limiter = RateLimiter.new(@user, "create_chat_message", allowed_message_count, 30.seconds) @@ -38,7 +43,7 @@ def silence_user @user, Discourse.system_user, silenced_till: silenced_for_minutes.minutes.from_now, - reason: I18n.t("chat.errors.rate_limit_exceeded") + reason: I18n.t("chat.errors.rate_limit_exceeded"), ) end end diff --git a/lib/chat_message_reactor.rb b/lib/chat_message_reactor.rb index d3e1a7679..5fe42f66c 100644 --- a/lib/chat_message_reactor.rb +++ b/lib/chat_message_reactor.rb @@ -44,10 +44,8 @@ def validate_reaction!(react_action, emoji) end def enforce_channel_membership! - existing_membership = UserChatChannelMembership.find_or_initialize_by( - chat_channel: @chat_channel, - user: @user, - ) + existing_membership = + UserChatChannelMembership.find_or_initialize_by(chat_channel: @chat_channel, user: @user) unless existing_membership&.following existing_membership.following = true @@ -58,23 +56,24 @@ def enforce_channel_membership! def validate_channel_status! return if @guardian.can_create_channel_message?(@chat_channel) raise Discourse::InvalidAccess.new( - nil, - nil, - custom_message: "chat.errors.channel_modify_message_disallowed", - custom_message_params: { status: @chat_channel.status_name } - ) + nil, + nil, + custom_message: "chat.errors.channel_modify_message_disallowed", + custom_message_params: { + status: @chat_channel.status_name, + }, + ) end def validate_max_reactions!(message, react_action, emoji) if react_action == ADD_REACTION && - message.reactions.count('DISTINCT emoji') >= MAX_REACTIONS_LIMIT && - !message.reactions.exists?(emoji: emoji) - + message.reactions.count("DISTINCT emoji") >= MAX_REACTIONS_LIMIT && + !message.reactions.exists?(emoji: emoji) raise Discourse::InvalidAccess.new( - nil, - nil, - custom_message: "chat.errors.max_reactions_limit_reached" - ) + nil, + nil, + custom_message: "chat.errors.max_reactions_limit_reached", + ) end end @@ -87,12 +86,6 @@ def create_reaction(message, react_action, emoji) end def publish_reaction(message, react_action, emoji) - ChatPublisher.publish_reaction!( - @chat_channel, - message, - react_action, - @user, - emoji - ) + ChatPublisher.publish_reaction!(@chat_channel, message, react_action, @user, emoji) end end diff --git a/lib/chat_message_updater.rb b/lib/chat_message_updater.rb index 88e3bb3fd..9dd098772 100644 --- a/lib/chat_message_updater.rb +++ b/lib/chat_message_updater.rb @@ -33,7 +33,7 @@ def update Jobs.enqueue(:process_chat_message, { chat_message_id: @chat_message.id }) DiscourseChat::ChatNotifier.notify_edit( chat_message: @chat_message, - timestamp: revision.created_at + timestamp: revision.created_at, ) rescue => error @error = error @@ -49,8 +49,11 @@ def failed? def validate_channel_status! return if @guardian.can_modify_channel_message?(@chat_channel) raise StandardError.new( - I18n.t("chat.errors.channel_modify_message_disallowed", status: @chat_channel.status_name) - ) + I18n.t( + "chat.errors.channel_modify_message_disallowed", + status: @chat_channel.status_name, + ), + ) end def validate_message!(has_uploads:) @@ -83,6 +86,9 @@ def update_uploads(upload_info) end def save_revision! - @chat_message.revisions.create!(old_message: @old_message_content, new_message: @chat_message.message) + @chat_message.revisions.create!( + old_message: @old_message_content, + new_message: @chat_message.message, + ) end end diff --git a/lib/chat_notifier.rb b/lib/chat_notifier.rb index 955242d2c..7c157f5ff 100644 --- a/lib/chat_notifier.rb +++ b/lib/chat_notifier.rb @@ -37,7 +37,8 @@ def notify_new end notify_creator_of_inaccessible_mentions( - inaccessible[:unreachable], inaccessible[:welcome_to_join] + inaccessible[:unreachable], + inaccessible[:welcome_to_join], ) notify_mentioned_users(to_notify) @@ -47,7 +48,8 @@ def notify_new end def notify_edit - existing_notifications = ChatMention.includes(:user, :notification).where(chat_message: @chat_message) + existing_notifications = + ChatMention.includes(:user, :notification).where(chat_message: @chat_message) already_notified_user_ids = existing_notifications.map(&:user_id) to_notify = list_users_to_notify @@ -65,7 +67,8 @@ def notify_edit return if needs_notification_ids.blank? notify_creator_of_inaccessible_mentions( - inaccessible[:unreachable], inaccessible[:welcome_to_join] + inaccessible[:unreachable], + inaccessible[:welcome_to_join], ) notify_mentioned_users(to_notify, already_notified_user_ids: already_notified_user_ids) @@ -92,11 +95,12 @@ def list_users_to_notify end def chat_users - users = User.includes(:do_not_disturb_timings, :push_subscriptions, :user_chat_channel_memberships) + users = + User.includes(:do_not_disturb_timings, :push_subscriptions, :user_chat_channel_memberships) users .distinct - .joins('LEFT OUTER JOIN user_chat_channel_memberships uccm ON uccm.user_id = users.id') + .joins("LEFT OUTER JOIN user_chat_channel_memberships uccm ON uccm.user_id = users.id") .joins(:user_option) .real .not_suspended @@ -105,8 +109,12 @@ def chat_users end def rest_of_the_channel - chat_users - .where(user_chat_channel_memberships: { following: true, chat_channel_id: @chat_channel.id }) + chat_users.where( + user_chat_channel_memberships: { + following: true, + chat_channel_id: @chat_channel.id, + }, + ) end def members_accepting_channel_wide_notifications @@ -114,7 +122,8 @@ def members_accepting_channel_wide_notifications end def direct_mentions_from_cooked - @direct_mentions_from_cooked ||= Nokogiri::HTML5.fragment(@chat_message.cooked).css(".mention").map(&:text) + @direct_mentions_from_cooked ||= + Nokogiri::HTML5.fragment(@chat_message.cooked).css(".mention").map(&:text) end def normalized_mentions(mentions) @@ -155,26 +164,31 @@ def expand_here_mention(to_notify, already_covered_ids) end def group_users_to_notify(users) - potential_participants, unreachable = users.partition do |user| - guardian = Guardian.new(user) - guardian.can_chat?(user) && guardian.can_see_chat_channel?(@chat_channel) - end - - participants, welcome_to_join = potential_participants.partition do |participant| - participant.user_chat_channel_memberships.any? { |m| m.chat_channel_id == @chat_channel.id && m.following == true } - end + potential_participants, unreachable = + users.partition do |user| + guardian = Guardian.new(user) + guardian.can_chat?(user) && guardian.can_see_chat_channel?(@chat_channel) + end + + participants, welcome_to_join = + potential_participants.partition do |participant| + participant.user_chat_channel_memberships.any? do |m| + m.chat_channel_id == @chat_channel.id && m.following == true + end + end { already_participating: participants || [], welcome_to_join: welcome_to_join || [], - unreachable: unreachable || [] + unreachable: unreachable || [], } end def expand_direct_mentions(to_notify, already_covered_ids) - direct_mentions = chat_users - .where(username_lower: normalized_mentions(direct_mentions_from_cooked)) - .where.not(id: already_covered_ids) + direct_mentions = + chat_users + .where(username_lower: normalized_mentions(direct_mentions_from_cooked)) + .where.not(id: already_covered_ids) grouped = group_users_to_notify(direct_mentions) @@ -185,17 +199,18 @@ def expand_direct_mentions(to_notify, already_covered_ids) end def group_name_mentions - @group_mentions_from_cooked ||= normalized_mentions( - Nokogiri::HTML5 - .fragment(@chat_message.cooked) - .css(".mention-group") - .map(&:text) - ) + @group_mentions_from_cooked ||= + normalized_mentions( + Nokogiri::HTML5.fragment(@chat_message.cooked).css(".mention-group").map(&:text), + ) end def mentionable_groups - @mentionable_groups ||= Group.mentionable(@user, include_public: false) - .where('LOWER(name) IN (?)', group_name_mentions) + @mentionable_groups ||= + Group.mentionable(@user, include_public: false).where( + "LOWER(name) IN (?)", + group_name_mentions, + ) end def expand_group_mentions(to_notify, already_covered_ids) @@ -203,9 +218,8 @@ def expand_group_mentions(to_notify, already_covered_ids) mentionable_groups.each { |g| to_notify[g.name.downcase] = [] } - reached_by_group = chat_users - .joins(:groups).where(groups: mentionable_groups) - .where.not(id: already_covered_ids) + reached_by_group = + chat_users.joins(:groups).where(groups: mentionable_groups).where.not(id: already_covered_ids) grouped = group_users_to_notify(reached_by_group) @@ -227,23 +241,34 @@ def expand_group_mentions(to_notify, already_covered_ids) def notify_creator_of_inaccessible_mentions(unreachable, welcome_to_join) return if unreachable.empty? && welcome_to_join.empty? - ChatPublisher.publish_inaccessible_mentions(@user.id, @chat_message, unreachable, welcome_to_join) + ChatPublisher.publish_inaccessible_mentions( + @user.id, + @chat_message, + unreachable, + welcome_to_join, + ) end def notify_mentioned_users(to_notify, already_notified_user_ids: []) - Jobs.enqueue(:chat_notify_mentioned, { - chat_message_id: @chat_message.id, - to_notify_ids_map: to_notify.as_json, - already_notified_user_ids: already_notified_user_ids, - timestamp: @timestamp.iso8601(6) - }) + Jobs.enqueue( + :chat_notify_mentioned, + { + chat_message_id: @chat_message.id, + to_notify_ids_map: to_notify.as_json, + already_notified_user_ids: already_notified_user_ids, + timestamp: @timestamp.iso8601(6), + }, + ) end def notify_watching_users(except: []) - Jobs.enqueue(:chat_notify_watching, { - chat_message_id: @chat_message.id, - except_user_ids: except, - timestamp: @timestamp.iso8601(6) - }) + Jobs.enqueue( + :chat_notify_watching, + { + chat_message_id: @chat_message.id, + except_user_ids: except, + timestamp: @timestamp.iso8601(6), + }, + ) end end diff --git a/lib/chat_seeder.rb b/lib/chat_seeder.rb index f586a860a..1f889d2ad 100644 --- a/lib/chat_seeder.rb +++ b/lib/chat_seeder.rb @@ -18,17 +18,14 @@ def create_category_channel_from(category_id) category = Category.find_by(id: category_id) return if category.nil? - name = if category_id == SiteSetting.meta_category_id - I18n.t('chat.channel.default_titles.site_feedback') - else - nil - end + name = + if category_id == SiteSetting.meta_category_id + I18n.t("chat.channel.default_titles.site_feedback") + else + nil + end - chat_channel = ChatChannel.create!( - chatable: category, - auto_join_users: true, - name: name - ) + chat_channel = ChatChannel.create!(chatable: category, auto_join_users: true, name: name) category.custom_fields[DiscourseChat::HAS_CHAT_ENABLED] = true category.save! UserChatChannelMembership.enforce_automatic_channel_memberships(chat_channel) diff --git a/lib/chat_statistics.rb b/lib/chat_statistics.rb index d90dc6c6b..80e8b1850 100644 --- a/lib/chat_statistics.rb +++ b/lib/chat_statistics.rb @@ -3,40 +3,49 @@ class DiscourseChat::Statistics def self.about_messages { - last_day: ChatMessage.where('created_at > ?', 1.days.ago).count, - "7_days" => ChatMessage.where('created_at > ?', 7.days.ago).count, - "30_days" => ChatMessage.where('created_at > ?', 30.days.ago).count, - previous_30_days: ChatMessage.where('created_at BETWEEN ? AND ?', 60.days.ago, 30.days.ago).count, - count: ChatMessage.count + :last_day => ChatMessage.where("created_at > ?", 1.days.ago).count, + "7_days" => ChatMessage.where("created_at > ?", 7.days.ago).count, + "30_days" => ChatMessage.where("created_at > ?", 30.days.ago).count, + :previous_30_days => + ChatMessage.where("created_at BETWEEN ? AND ?", 60.days.ago, 30.days.ago).count, + :count => ChatMessage.count, } end def self.about_channels { - last_day: ChatChannel.where(status: :open).where('created_at > ?', 1.days.ago).count, - "7_days" => ChatChannel.where(status: :open).where('created_at > ?', 7.days.ago).count, - "30_days" => ChatChannel.where(status: :open).where('created_at > ?', 30.days.ago).count, - previous_30_days: ChatChannel.where(status: :open).where('created_at BETWEEN ? AND ?', 60.days.ago, 30.days.ago).count, - count: ChatChannel.where(status: :open).count + :last_day => ChatChannel.where(status: :open).where("created_at > ?", 1.days.ago).count, + "7_days" => ChatChannel.where(status: :open).where("created_at > ?", 7.days.ago).count, + "30_days" => ChatChannel.where(status: :open).where("created_at > ?", 30.days.ago).count, + :previous_30_days => + ChatChannel + .where(status: :open) + .where("created_at BETWEEN ? AND ?", 60.days.ago, 30.days.ago) + .count, + :count => ChatChannel.where(status: :open).count, } end def self.about_users { - last_day: ChatMessage.where('created_at > ?', 1.days.ago).distinct.count(:user_id), - "7_days" => ChatMessage.where('created_at > ?', 7.days.ago).distinct.count(:user_id), - "30_days" => ChatMessage.where('created_at > ?', 30.days.ago).distinct.count(:user_id), - previous_30_days: ChatMessage.where('created_at BETWEEN ? AND ?', 60.days.ago, 30.days.ago).distinct.count(:user_id), - count: ChatMessage.distinct.count(:user_id), + :last_day => ChatMessage.where("created_at > ?", 1.days.ago).distinct.count(:user_id), + "7_days" => ChatMessage.where("created_at > ?", 7.days.ago).distinct.count(:user_id), + "30_days" => ChatMessage.where("created_at > ?", 30.days.ago).distinct.count(:user_id), + :previous_30_days => + ChatMessage + .where("created_at BETWEEN ? AND ?", 60.days.ago, 30.days.ago) + .distinct + .count(:user_id), + :count => ChatMessage.distinct.count(:user_id), } end def self.monthly start_of_month = Time.zone.now.beginning_of_month { - messages: ChatMessage.where('created_at > ?', start_of_month).count, - channels: ChatChannel.where(status: :open).where('created_at > ?', start_of_month).count, - users: ChatMessage.where('created_at > ?', start_of_month).distinct.count(:user_id) + messages: ChatMessage.where("created_at > ?", start_of_month).count, + channels: ChatChannel.where(status: :open).where("created_at > ?", start_of_month).count, + users: ChatMessage.where("created_at > ?", start_of_month).distinct.count(:user_id), } end end diff --git a/lib/chat_transcript_service.rb b/lib/chat_transcript_service.rb index dfc41fa3f..6326494cd 100644 --- a/lib/chat_transcript_service.rb +++ b/lib/chat_transcript_service.rb @@ -66,14 +66,13 @@ def render private def reactions_attr - reaction_data = @message_data.reduce([]) do |array, msg_data| - if msg_data[:reactions].any? - array << msg_data[:reactions].map do |react| - "#{react.emoji}:#{react.usernames}" + reaction_data = + @message_data.reduce([]) do |array, msg_data| + if msg_data[:reactions].any? + array << msg_data[:reactions].map { |react| "#{react.emoji}:#{react.usernames}" } end + array end - array - end return if reaction_data.empty? "reactions=\"#{reaction_data.join(";")}\"" end @@ -107,25 +106,27 @@ def generate_markdown previous_message = nil rendered_markdown = [] all_messages_same_user = messages.count(:user_id) == 1 - open_bbcode_tag = ChatTranscriptBBCode.new( - channel: @channel, - acting_user: @acting_user, - multiquote: messages.length > 1, - chained: !all_messages_same_user, - no_link: @opts[:no_link], - include_reactions: @opts[:include_reactions] - ) + open_bbcode_tag = + ChatTranscriptBBCode.new( + channel: @channel, + acting_user: @acting_user, + multiquote: messages.length > 1, + chained: !all_messages_same_user, + no_link: @opts[:no_link], + include_reactions: @opts[:include_reactions], + ) messages.each.with_index do |message, idx| if previous_message.present? && previous_message.user_id != message.user_id rendered_markdown << open_bbcode_tag.render - open_bbcode_tag = ChatTranscriptBBCode.new( - acting_user: @acting_user, - chained: !all_messages_same_user, - no_link: @opts[:no_link], - include_reactions: @opts[:include_reactions] - ) + open_bbcode_tag = + ChatTranscriptBBCode.new( + acting_user: @acting_user, + chained: !all_messages_same_user, + no_link: @opts[:no_link], + include_reactions: @opts[:include_reactions], + ) end if @opts[:include_reactions] @@ -144,11 +145,11 @@ def generate_markdown private def messages - @messages ||= ChatMessage.includes( - :user, chat_uploads: :upload - ).where( - id: @message_ids, chat_channel_id: @channel.id - ).order(:created_at) + @messages ||= + ChatMessage + .includes(:user, chat_uploads: :upload) + .where(id: @message_ids, chat_channel_id: @channel.id) + .order(:created_at) end ## diff --git a/lib/direct_message_channel_creator.rb b/lib/direct_message_channel_creator.rb index 5f79ca194..5db5663f4 100644 --- a/lib/direct_message_channel_creator.rb +++ b/lib/direct_message_channel_creator.rb @@ -7,7 +7,8 @@ def self.create!(target_users:) if direct_messages_channel chat_channel = ChatChannel.find_by!(chatable: direct_messages_channel) else - direct_messages_channel = DirectMessageChannel.create!(user_ids: unique_target_users.map(&:id)) + direct_messages_channel = + DirectMessageChannel.create!(user_ids: unique_target_users.map(&:id)) chat_channel = ChatChannel.create!(chatable: direct_messages_channel) end @@ -20,12 +21,18 @@ def self.create!(target_users:) def self.update_memberships(unique_target_users, chat_channel_id) unique_target_users.each do |user| - membership = UserChatChannelMembership.find_or_initialize_by(user_id: user.id, chat_channel_id: chat_channel_id) + membership = + UserChatChannelMembership.find_or_initialize_by( + user_id: user.id, + chat_channel_id: chat_channel_id, + ) if membership.new_record? membership.last_read_message_id = nil - membership.desktop_notification_level = UserChatChannelMembership::NOTIFICATION_LEVELS[:always] - membership.mobile_notification_level = UserChatChannelMembership::NOTIFICATION_LEVELS[:always] + membership.desktop_notification_level = + UserChatChannelMembership::NOTIFICATION_LEVELS[:always] + membership.mobile_notification_level = + UserChatChannelMembership::NOTIFICATION_LEVELS[:always] membership.muted = false end diff --git a/lib/discourse_dev/direct_channel.rb b/lib/discourse_dev/direct_channel.rb index def5c10ac..c4ea2a9aa 100644 --- a/lib/discourse_dev/direct_channel.rb +++ b/lib/discourse_dev/direct_channel.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'discourse_dev/record' -require 'faker' +require "discourse_dev/record" +require "faker" module DiscourseDev class DirectChannel < Record @@ -15,10 +15,7 @@ def data admin_user = ::User.find_by(username: admin_username) end - [ - User.new.create!, - admin_user || User.new.create! - ] + [User.new.create!, admin_user || User.new.create!] end def create! diff --git a/lib/discourse_dev/message.rb b/lib/discourse_dev/message.rb index 121515db2..f36d06158 100644 --- a/lib/discourse_dev/message.rb +++ b/lib/discourse_dev/message.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'discourse_dev/record' -require 'faker' +require "discourse_dev/record" +require "faker" module DiscourseDev class Message < Record @@ -11,7 +11,7 @@ def initialize def data if Faker::Boolean.boolean(true_ratio: 0.5) - channel = ::ChatChannel.where(chatable_type: 'DirectMessageChannel').order("RANDOM()").first + channel = ::ChatChannel.where(chatable_type: "DirectMessageChannel").order("RANDOM()").first channel.user_chat_channel_memberships.update_all(following: true) user = channel.chatable.users.order("RANDOM()").first else @@ -20,11 +20,7 @@ def data user = membership.user end - { - user: user, - content: Faker::Lorem.paragraph, - chat_channel: channel, - } + { user: user, content: Faker::Lorem.paragraph, chat_channel: channel } end def create! diff --git a/lib/discourse_dev/public_channel.rb b/lib/discourse_dev/public_channel.rb index 0c42b937c..2d18b45b1 100644 --- a/lib/discourse_dev/public_channel.rb +++ b/lib/discourse_dev/public_channel.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require 'discourse_dev/record' -require 'faker' +require "discourse_dev/record" +require "faker" module DiscourseDev class PublicChannel < Record @@ -23,18 +23,25 @@ def data def create! super do |channel| - Faker::Number.between(from: 5, to: 10).times do - if Faker::Boolean.boolean(true_ratio: 0.5) - admin_username = DiscourseDev::Config.new.config[:admin][:username] rescue nil - admin_user = ::User.find_by(username: admin_username) if admin_username - end + Faker::Number + .between(from: 5, to: 10) + .times do + if Faker::Boolean.boolean(true_ratio: 0.5) + admin_username = + begin + DiscourseDev::Config.new.config[:admin][:username] + rescue StandardError + nil + end + admin_user = ::User.find_by(username: admin_username) if admin_username + end - ::UserChatChannelMembership.find_or_create_by!( - user: admin_user || User.new.create!, - chat_channel: channel, - following: true, - ) - end + ::UserChatChannelMembership.find_or_create_by!( + user: admin_user || User.new.create!, + chat_channel: channel, + following: true, + ) + end end end end diff --git a/lib/duplicate_message_validator.rb b/lib/duplicate_message_validator.rb index 4531c741b..31da101a9 100644 --- a/lib/duplicate_message_validator.rb +++ b/lib/duplicate_message_validator.rb @@ -9,9 +9,10 @@ def initialize(chat_message) def validate return if SiteSetting.chat_duplicate_message_sensitivity.zero? - matrix = DiscourseChat::DuplicateMessageValidator.sensitivity_matrix( - SiteSetting.chat_duplicate_message_sensitivity - ) + matrix = + DiscourseChat::DuplicateMessageValidator.sensitivity_matrix( + SiteSetting.chat_duplicate_message_sensitivity, + ) # Check if the length of the message is too short to check for a duplicate message return if chat_message.message.length < matrix[:min_message_length] @@ -20,10 +21,14 @@ def validate return if (chat_message.chat_channel.user_count || 0) < matrix[:min_user_count] # Check if the same duplicate message has been posted in the last N seconds by any user - return if !chat_message.chat_channel.chat_messages - .where("created_at > ?", matrix[:min_past_seconds].seconds.ago) - .where(message: chat_message.message) - .exists? + if !chat_message + .chat_channel + .chat_messages + .where("created_at > ?", matrix[:min_past_seconds].seconds.ago) + .where(message: chat_message.message) + .exists? + return + end chat_message.errors.add(:base, I18n.t("chat.errors.duplicate_message")) end @@ -32,12 +37,10 @@ def self.sensitivity_matrix(sensitivity) { # 0.1 sensitivity = 100 users and 1.0 sensitivity = 5 users. min_user_count: (-1.0 * 105.5 * sensitivity + 110.55).to_i, - # 0.1 sensitivity = 30 chars and 1.0 sensitivity = 10 chars. min_message_length: (-1.0 * 22.2 * sensitivity + 32.22).to_i, - # 0.1 sensitivity = 10 seconds and 1.0 sensitivity = 60 seconds. - min_past_seconds: (55.55 * sensitivity + 4.5).to_i + min_past_seconds: (55.55 * sensitivity + 4.5).to_i, } end end diff --git a/lib/email_controller_helper/chat_summary_unsubscriber.rb b/lib/email_controller_helper/chat_summary_unsubscriber.rb index b4a3e82c0..ab4b06a75 100644 --- a/lib/email_controller_helper/chat_summary_unsubscriber.rb +++ b/lib/email_controller_helper/chat_summary_unsubscriber.rb @@ -5,12 +5,16 @@ class ChatSummaryUnsubscriber < BaseEmailUnsubscriber def prepare_unsubscribe_options(controller) super(controller) - chat_email_frequencies = UserOption.chat_email_frequencies.map do |(freq, _)| - [I18n.t("unsubscribe.chat_summary.#{freq}"), freq] - end + chat_email_frequencies = + UserOption.chat_email_frequencies.map do |(freq, _)| + [I18n.t("unsubscribe.chat_summary.#{freq}"), freq] + end controller.instance_variable_set(:@chat_email_frequencies, chat_email_frequencies) - controller.instance_variable_set(:@current_chat_email_frequency, key_owner.user_option.chat_email_frequency) + controller.instance_variable_set( + :@current_chat_email_frequency, + key_owner.user_option.chat_email_frequency, + ) end def unsubscribe(params) diff --git a/lib/extensions/user_email_extension.rb b/lib/extensions/user_email_extension.rb index b52ee566b..e067e7d25 100644 --- a/lib/extensions/user_email_extension.rb +++ b/lib/extensions/user_email_extension.rb @@ -4,11 +4,11 @@ module DiscourseChat::UserEmailExtension def execute(args) super(args) - if args[:type] == 'chat_summary' && args[:memberships_to_update_data].present? + if args[:type] == "chat_summary" && args[:memberships_to_update_data].present? args[:memberships_to_update_data].to_a.each do |membership_id, max_unread_mention_id| - UserChatChannelMembership - .find_by(user: args[:user_id], id: membership_id.to_i) - &.update(last_unread_mention_when_emailed_id: max_unread_mention_id.to_i) + UserChatChannelMembership.find_by(user: args[:user_id], id: membership_id.to_i)&.update( + last_unread_mention_when_emailed_id: max_unread_mention_id.to_i, + ) end end end diff --git a/lib/extensions/user_notifications_extension.rb b/lib/extensions/user_notifications_extension.rb index 4f7f74d76..bcc6ce51a 100644 --- a/lib/extensions/user_notifications_extension.rb +++ b/lib/extensions/user_notifications_extension.rb @@ -5,23 +5,26 @@ def chat_summary(user, opts) guardian = Guardian.new(user) return unless guardian.can_chat?(user) - @messages = ChatMessage - .joins(:user, :chat_channel) - .where.not(user: user) - .where('chat_messages.created_at > ?', 1.week.ago) - .joins('LEFT OUTER JOIN chat_mentions cm ON cm.chat_message_id = chat_messages.id') - .joins('INNER JOIN user_chat_channel_memberships uccm ON uccm.chat_channel_id = chat_channels.id') - .where(uccm: { following: true, user_id: user.id }) - .where( - <<~SQL + @messages = + ChatMessage + .joins(:user, :chat_channel) + .where.not(user: user) + .where("chat_messages.created_at > ?", 1.week.ago) + .joins("LEFT OUTER JOIN chat_mentions cm ON cm.chat_message_id = chat_messages.id") + .joins( + "INNER JOIN user_chat_channel_memberships uccm ON uccm.chat_channel_id = chat_channels.id", + ) + .where(uccm: { following: true, user_id: user.id }) + .where(<<~SQL) (cm.user_id = #{user.id} OR chat_channels.chatable_type = 'DirectMessageChannel') AND (uccm.last_read_message_id IS NULL OR chat_messages.id > uccm.last_read_message_id) AND (uccm.last_unread_mention_when_emailed_id IS NULL OR chat_messages.id > uccm.last_unread_mention_when_emailed_id) SQL - ).to_a + .to_a return if @messages.empty? @grouped_messages = @messages.group_by { |message| message.chat_channel } - @grouped_messages = @grouped_messages.select { |channel, _| guardian.can_see_chat_channel?(channel) } + @grouped_messages = + @grouped_messages.select { |channel, _| guardian.can_see_chat_channel?(channel) } return if @grouped_messages.empty? @grouped_messages.each do |chat_channel, messages| @@ -38,13 +41,13 @@ def chat_summary(user, opts) add_unsubscribe_link = UnsubscribeKey.respond_to?(:get_unsubscribe_strategy_for) if add_unsubscribe_link - unsubscribe_key = UnsubscribeKey.create_key_for(@user, 'chat_summary') + unsubscribe_key = UnsubscribeKey.create_key_for(@user, "chat_summary") @unsubscribe_link = "#{Discourse.base_url}/email/unsubscribe/#{unsubscribe_key}" opts[:unsubscribe_url] = @unsubscribe_link end opts = { - from_alias: I18n.t('user_notifications.chat_summary.from', site_name: Email.site_title), + from_alias: I18n.t("user_notifications.chat_summary.from", site_name: Email.site_title), subject: summary_subject(user, @grouped_messages), add_unsubscribe_link: add_unsubscribe_link, } @@ -62,9 +65,9 @@ def summary_subject(user, grouped_messages) first_message_from = non_dm_channels.pop if first_message_from first_message_title = first_message_from.title(user) - subject_key = 'chat_channel' + subject_key = "chat_channel" else - subject_key = 'direct_message' + subject_key = "direct_message" first_message_from = dm_users.pop first_message_title = first_message_from.username end @@ -73,7 +76,14 @@ def summary_subject(user, grouped_messages) email_prefix: @email_prefix, count: total_count_for_subject, message_title: first_message_title, - others: other_channels_text(user, total_count_for_subject, first_message_from, non_dm_channels, dm_users) + others: + other_channels_text( + user, + total_count_for_subject, + first_message_from, + non_dm_channels, + dm_users, + ), } I18n.t(with_subject_prefix(subject_key), **subject_opts) @@ -83,9 +93,15 @@ def with_subject_prefix(key) "user_notifications.chat_summary.subject.#{key}" end - def other_channels_text(user, total_count, first_message_from, other_non_dm_channels, other_dm_users) + def other_channels_text( + user, + total_count, + first_message_from, + other_non_dm_channels, + other_dm_users + ) return if total_count <= 1 - return I18n.t(with_subject_prefix('others'), count: total_count - 1) if total_count > 2 + return I18n.t(with_subject_prefix("others"), count: total_count - 1) if total_count > 2 if other_non_dm_channels.empty? second_message_from = other_dm_users.first @@ -97,6 +113,6 @@ def other_channels_text(user, total_count, first_message_from, other_non_dm_chan return second_message_title if first_message_from.class == second_message_from.class - I18n.t(with_subject_prefix('other_direct_message'), message_title: second_message_title) + I18n.t(with_subject_prefix("other_direct_message"), message_title: second_message_title) end end diff --git a/lib/extensions/user_option_extension.rb b/lib/extensions/user_option_extension.rb index e42452399..7d10d064a 100644 --- a/lib/extensions/user_option_extension.rb +++ b/lib/extensions/user_option_extension.rb @@ -4,16 +4,13 @@ module DiscourseChat::UserOptionExtension # TODO: remove last_emailed_for_chat and chat_isolated in 2023 def self.prepended(base) if base.ignored_columns - base.ignored_columns = base.ignored_columns + [:last_emailed_for_chat, :chat_isolated] + base.ignored_columns = base.ignored_columns + %i[last_emailed_for_chat chat_isolated] else - base.ignored_columns = [:last_emailed_for_chat, :chat_isolated] + base.ignored_columns = %i[last_emailed_for_chat chat_isolated] end def base.chat_email_frequencies - @chat_email_frequencies ||= { - never: 0, - when_away: 1 - } + @chat_email_frequencies ||= { never: 0, when_away: 1 } end base.enum chat_email_frequency: base.chat_email_frequencies diff --git a/lib/guardian_extensions.rb b/lib/guardian_extensions.rb index cc2f3de7f..d52268169 100644 --- a/lib/guardian_extensions.rb +++ b/lib/guardian_extensions.rb @@ -114,9 +114,11 @@ def can_delete_chat?(message, chatable) return false if @user.silenced? return false if !can_modify_channel_message?(message.chat_channel) - message.user_id == current_user.id ? - can_delete_own_chats?(chatable) : + if message.user_id == current_user.id + can_delete_own_chats?(chatable) + else can_delete_other_chats?(chatable) + end end def can_delete_own_chats?(chatable) @@ -135,9 +137,11 @@ def can_delete_other_chats?(chatable) def can_restore_chat?(message, chatable) return false if !can_modify_channel_message?(message.chat_channel) - message.user_id == current_user.id ? - can_restore_own_chats?(chatable) : + if message.user_id == current_user.id + can_restore_own_chats?(chatable) + else can_delete_other_chats?(chatable) + end end def can_restore_own_chats?(chatable) diff --git a/lib/message_mover.rb b/lib/message_mover.rb index 7be5cc218..7ef4259e9 100644 --- a/lib/message_mover.rb +++ b/lib/message_mover.rb @@ -17,8 +17,10 @@ # notifications, revisions, mentions, uploads) will be updated to the new # message IDs via a moved_chat_messages temporary table. class DiscourseChat::MessageMover - class NoMessagesFound < StandardError; end - class InvalidChannel < StandardError; end + class NoMessagesFound < StandardError + end + class InvalidChannel < StandardError + end def initialize(acting_user:, source_channel:, message_ids:) @source_channel = source_channel @@ -41,10 +43,11 @@ def move_to_channel(destination_channel) ChatMessage.transaction do create_temp_table - moved_messages = find_messages( - create_destination_messages_in_channel(destination_channel), - destination_channel - ) + moved_messages = + find_messages( + create_destination_messages_in_channel(destination_channel), + destination_channel, + ) bulk_insert_movement_metadata update_references delete_source_messages @@ -74,10 +77,10 @@ def create_temp_table end def bulk_insert_movement_metadata - values_sql = @movement_metadata.map do |mm| - "(#{mm[:old_id]}, #{mm[:new_id]})" - end.join(",\n") - DB.exec("INSERT INTO moved_chat_messages(old_chat_message_id, new_chat_message_id) VALUES #{values_sql}") + values_sql = @movement_metadata.map { |mm| "(#{mm[:old_id]}, #{mm[:new_id]})" }.join(",\n") + DB.exec( + "INSERT INTO moved_chat_messages(old_chat_message_id, new_chat_message_id) VALUES #{values_sql}", + ) end ## @@ -87,7 +90,7 @@ def bulk_insert_movement_metadata def create_destination_messages_in_channel(destination_channel) query_args = { message_ids: @ordered_source_message_ids, - destination_channel_id: destination_channel.id + destination_channel_id: destination_channel.id, } moved_message_ids = DB.query_single(<<~SQL, query_args) INSERT INTO chat_messages(chat_channel_id, user_id, message, cooked, cooked_version, created_at, updated_at) @@ -103,12 +106,10 @@ def create_destination_messages_in_channel(destination_channel) RETURNING id SQL - @movement_metadata = moved_message_ids.map.with_index do |chat_message_id, idx| - { - old_id: @ordered_source_message_ids[idx], - new_id: chat_message_id - } - end + @movement_metadata = + moved_message_ids.map.with_index do |chat_message_id, idx| + { old_id: @ordered_source_message_ids[idx], new_id: chat_message_id } + end moved_message_ids end @@ -158,13 +159,14 @@ def add_moved_placeholder(destination_channel, first_moved_message) DiscourseChat::ChatMessageCreator.create( chat_channel: @source_channel, user: Discourse.system_user, - content: I18n.t( - "chat.channel.messages_moved", - count: @source_message_ids.length, - acting_username: @acting_user.username, - channel_name: destination_channel.title(@acting_user), - first_moved_message_url: first_moved_message.url - ) + content: + I18n.t( + "chat.channel.messages_moved", + count: @source_message_ids.length, + acting_username: @acting_user.username, + channel_name: destination_channel.title(@acting_user), + first_moved_message_url: first_moved_message.url, + ), ) end end diff --git a/lib/post_notification_handler.rb b/lib/post_notification_handler.rb index 671e1fe95..9c3bd6384 100644 --- a/lib/post_notification_handler.rb +++ b/lib/post_notification_handler.rb @@ -24,22 +24,17 @@ def handle opts = { user_id: post.user.id, display_username: post.user.username } quoted_users.each do |user| - # PostAlerter.create_notification handles many edge cases, such as # muting, ignoring, double notifications etc. - PostAlerter.new.create_notification( - user, - Notification.types[:chat_quoted], - post, - opts - ) + PostAlerter.new.create_notification(user, Notification.types[:chat_quoted], post, opts) end end private def extract_quoted_users(post) - usernames = post.raw.scan(/\[chat quote=\"([^;]+);.+\"\]/).uniq.map { |q| q.first.strip.downcase } + usernames = + post.raw.scan(/\[chat quote=\"([^;]+);.+\"\]/).uniq.map { |q| q.first.strip.downcase } User.where.not(id: post.user_id).where(username_lower: usernames) end end diff --git a/lib/tasks/chat.rake b/lib/tasks/chat.rake index bad5be82a..a53e1b319 100644 --- a/lib/tasks/chat.rake +++ b/lib/tasks/chat.rake @@ -1,23 +1,23 @@ # frozen_string_literal: true if Discourse.allow_dev_populate? - chat_task = Rake::Task['dev:populate'] - chat_task.enhance { + chat_task = Rake::Task["dev:populate"] + chat_task.enhance do SiteSetting.chat_enabled = true DiscourseDev::PublicChannel.populate! DiscourseDev::DirectChannel.populate! DiscourseDev::Message.populate! - } + end - desc 'Generates sample content for chat' - task 'chat:populate' => ['db:load_config'] do |_, args| + desc "Generates sample content for chat" + task "chat:populate" => ["db:load_config"] do |_, args| DiscourseDev::PublicChannel.new.populate!(ignore_current_count: true) DiscourseDev::DirectChannel.new.populate!(ignore_current_count: true) DiscourseDev::Message.new.populate!(ignore_current_count: true) end - desc 'Generates sample messages in channels' - task 'chat:message:populate' => ['db:load_config'] do |_, args| + desc "Generates sample messages in channels" + task "chat:message:populate" => ["db:load_config"] do |_, args| DiscourseDev::Message.new.populate!(ignore_current_count: true) end end diff --git a/lib/tasks/chat_message.rake b/lib/tasks/chat_message.rake index 26b78541c..603722e4b 100644 --- a/lib/tasks/chat_message.rake +++ b/lib/tasks/chat_message.rake @@ -1,19 +1,16 @@ - # frozen_string_literal: true -task 'chat_messages:rebake_uncooked_chat_messages' => :environment do +task "chat_messages:rebake_uncooked_chat_messages" => :environment do # rebaking uncooked chat_messages can very quickly saturate sidekiq # this provides an insurance policy so you can safely run and stop # this rake task without worrying about your sidekiq imploding Jobs.run_immediately! - ENV['RAILS_DB'] ? rebake_uncooked_chat_messages : rebake_uncooked_chat_messages_all_sites + ENV["RAILS_DB"] ? rebake_uncooked_chat_messages : rebake_uncooked_chat_messages_all_sites end def rebake_uncooked_chat_messages_all_sites - RailsMultisite::ConnectionManagement.each_connection do |db| - rebake_uncooked_chat_messages - end + RailsMultisite::ConnectionManagement.each_connection { |db| rebake_uncooked_chat_messages } end def rebake_uncooked_chat_messages @@ -31,9 +28,7 @@ def rebake_uncooked_chat_messages # may have been cooked in interim chat_message = uncooked.where(id: id).first - if chat_message - rebake_chat_message(chat_message) - end + rebake_chat_message(chat_message) if chat_message print_status(rebaked += 1, total) end @@ -42,21 +37,22 @@ def rebake_uncooked_chat_messages end def rebake_chat_message(chat_message, opts = {}) - if !opts[:priority] - opts[:priority] = :ultra_low - end + opts[:priority] = :ultra_low if !opts[:priority] chat_message.rebake!(**opts) rescue => e - puts "", "Failed to rebake chat message (chat_message_id: #{chat_message.id})", e, e.backtrace.join("\n") + puts "", + "Failed to rebake chat message (chat_message_id: #{chat_message.id})", + e, + e.backtrace.join("\n") end -task 'chat:make_channel_to_test_archiving', [:user_for_membership] => :environment do |t, args| +task "chat:make_channel_to_test_archiving", [:user_for_membership] => :environment do |t, args| user_for_membership = args[:user_for_membership] # do not want this running in production! return if !Rails.env.development? - require 'fabrication' + require "fabrication" Dir[Rails.root.join("spec/fabricators/*.rb")].each { |f| require f } messages = [ @@ -77,7 +73,7 @@ task 'chat:make_channel_to_test_archiving', [:user_for_membership] => :environme "Nullam porttitor leo a leo `cursus`, id hendrerit dui ultrices.", "Pellentesque ut @#{user_for_membership} ut ex pulvinar pharetra sit amet ac leo.", "Vestibulum sit amet enim et lectus tincidunt rhoncus hendrerit in enim.", - <<~MSG + <<~MSG, some bigger message ```ruby @@ -91,11 +87,24 @@ task 'chat:make_channel_to_test_archiving', [:user_for_membership] => :environme chat_channel = nil Topic.transaction do - topic = Fabricate(:topic, user: make_test_user, title: "Testing topic for chat archiving #{SecureRandom.hex(4)}") - Fabricate(:post, topic: topic, user: topic.user, raw: "This is some cool first post for archive stuff") - chat_channel = ChatChannel.create( - chatable: topic, chatable_type: "Topic", name: "testing channel for archiving #{SecureRandom.hex(4)}" + topic = + Fabricate( + :topic, + user: make_test_user, + title: "Testing topic for chat archiving #{SecureRandom.hex(4)}", + ) + Fabricate( + :post, + topic: topic, + user: topic.user, + raw: "This is some cool first post for archive stuff", ) + chat_channel = + ChatChannel.create( + chatable: topic, + chatable_type: "Topic", + name: "testing channel for archiving #{SecureRandom.hex(4)}", + ) end puts "topic: #{topic.id}, #{topic.title}" @@ -120,7 +129,7 @@ task 'chat:make_channel_to_test_archiving', [:user_for_membership] => :environme chat_channel: chat_channel, last_read_message_id: 0, user: User.find_by(username: user_for_membership), - following: true + following: true, ) end diff --git a/plugin.rb b/plugin.rb index 4a4ce2ccb..f10e5bba5 100644 --- a/plugin.rb +++ b/plugin.rb @@ -9,52 +9,52 @@ enabled_site_setting :chat_enabled -register_asset 'stylesheets/mixins/chat-scrollbar.scss' -register_asset 'stylesheets/common/core-extensions.scss' -register_asset 'stylesheets/common/chat-channel-card.scss' -register_asset 'stylesheets/common/dc-filter-input.scss' -register_asset 'stylesheets/common/common.scss' -register_asset 'stylesheets/common/chat-browse.scss' -register_asset 'stylesheets/common/chat-drawer.scss' -register_asset 'stylesheets/common/chat-channel-preview-card.scss' -register_asset 'stylesheets/common/chat-channel-info.scss' -register_asset 'stylesheets/mobile/chat-channel-info.scss', :mobile -register_asset 'stylesheets/common/chat-draft-channel.scss' -register_asset 'stylesheets/common/chat-tabs.scss' -register_asset 'stylesheets/common/chat-form.scss' -register_asset 'stylesheets/common/d-progress-bar.scss' -register_asset 'stylesheets/common/incoming-chat-webhooks.scss' -register_asset 'stylesheets/mobile/chat-message.scss', :mobile -register_asset 'stylesheets/desktop/chat-message.scss', :desktop -register_asset 'stylesheets/common/chat-channel-title.scss' -register_asset 'stylesheets/common/full-page-chat-header.scss' -register_asset 'stylesheets/common/chat-reply.scss' -register_asset 'stylesheets/common/chat-message.scss' -register_asset 'stylesheets/common/chat-message-left-gutter.scss' -register_asset 'stylesheets/common/chat-message-info.scss' -register_asset 'stylesheets/common/chat-composer-inline-button.scss' -register_asset 'stylesheets/common/chat-replying-indicator.scss' -register_asset 'stylesheets/mobile/chat-replying-indicator.scss', :mobile -register_asset 'stylesheets/common/chat-composer.scss' -register_asset 'stylesheets/desktop/chat-composer.scss', :desktop -register_asset 'stylesheets/mobile/chat-composer.scss', :mobile -register_asset 'stylesheets/common/direct-message-creator.scss' -register_asset 'stylesheets/common/chat-message-collapser.scss' -register_asset 'stylesheets/common/chat-message-images.scss' -register_asset 'stylesheets/common/chat-transcript.scss' -register_asset 'stylesheets/common/chat-composer-dropdown.scss' -register_asset 'stylesheets/common/chat-retention-reminder.scss' -register_asset 'stylesheets/common/chat-composer-uploads.scss' -register_asset 'stylesheets/common/chat-composer-upload.scss' -register_asset 'stylesheets/common/chat-selection-manager.scss' -register_asset 'stylesheets/mobile/chat-selection-manager.scss', :mobile -register_asset 'stylesheets/common/chat-channel-selector-modal.scss' -register_asset 'stylesheets/mobile/mobile.scss', :mobile -register_asset 'stylesheets/desktop/desktop.scss', :desktop -register_asset 'stylesheets/sidebar-extensions.scss' -register_asset 'stylesheets/desktop/sidebar-extensions.scss', :desktop -register_asset 'stylesheets/common/chat-message-separator.scss' -register_asset 'stylesheets/common/chat-onebox.scss' +register_asset "stylesheets/mixins/chat-scrollbar.scss" +register_asset "stylesheets/common/core-extensions.scss" +register_asset "stylesheets/common/chat-channel-card.scss" +register_asset "stylesheets/common/dc-filter-input.scss" +register_asset "stylesheets/common/common.scss" +register_asset "stylesheets/common/chat-browse.scss" +register_asset "stylesheets/common/chat-drawer.scss" +register_asset "stylesheets/common/chat-channel-preview-card.scss" +register_asset "stylesheets/common/chat-channel-info.scss" +register_asset "stylesheets/mobile/chat-channel-info.scss", :mobile +register_asset "stylesheets/common/chat-draft-channel.scss" +register_asset "stylesheets/common/chat-tabs.scss" +register_asset "stylesheets/common/chat-form.scss" +register_asset "stylesheets/common/d-progress-bar.scss" +register_asset "stylesheets/common/incoming-chat-webhooks.scss" +register_asset "stylesheets/mobile/chat-message.scss", :mobile +register_asset "stylesheets/desktop/chat-message.scss", :desktop +register_asset "stylesheets/common/chat-channel-title.scss" +register_asset "stylesheets/common/full-page-chat-header.scss" +register_asset "stylesheets/common/chat-reply.scss" +register_asset "stylesheets/common/chat-message.scss" +register_asset "stylesheets/common/chat-message-left-gutter.scss" +register_asset "stylesheets/common/chat-message-info.scss" +register_asset "stylesheets/common/chat-composer-inline-button.scss" +register_asset "stylesheets/common/chat-replying-indicator.scss" +register_asset "stylesheets/mobile/chat-replying-indicator.scss", :mobile +register_asset "stylesheets/common/chat-composer.scss" +register_asset "stylesheets/desktop/chat-composer.scss", :desktop +register_asset "stylesheets/mobile/chat-composer.scss", :mobile +register_asset "stylesheets/common/direct-message-creator.scss" +register_asset "stylesheets/common/chat-message-collapser.scss" +register_asset "stylesheets/common/chat-message-images.scss" +register_asset "stylesheets/common/chat-transcript.scss" +register_asset "stylesheets/common/chat-composer-dropdown.scss" +register_asset "stylesheets/common/chat-retention-reminder.scss" +register_asset "stylesheets/common/chat-composer-uploads.scss" +register_asset "stylesheets/common/chat-composer-upload.scss" +register_asset "stylesheets/common/chat-selection-manager.scss" +register_asset "stylesheets/mobile/chat-selection-manager.scss", :mobile +register_asset "stylesheets/common/chat-channel-selector-modal.scss" +register_asset "stylesheets/mobile/mobile.scss", :mobile +register_asset "stylesheets/desktop/desktop.scss", :desktop +register_asset "stylesheets/sidebar-extensions.scss" +register_asset "stylesheets/desktop/sidebar-extensions.scss", :desktop +register_asset "stylesheets/common/chat-message-separator.scss" +register_asset "stylesheets/common/chat-onebox.scss" register_svg_icon "comments" register_svg_icon "comment-slash" @@ -66,7 +66,7 @@ register_svg_icon "file-image" # route: /admin/plugins/chat -add_admin_route 'chat.admin.title', 'chat' +add_admin_route "chat.admin.title", "chat" # Site setting validators must be loaded before initialize require_relative "lib/validators/chat_default_channel_validator.rb" @@ -87,97 +87,104 @@ def self.allowed_group_ids end def self.onebox_template - @onebox_template ||= begin - path = "#{Rails.root}/plugins/discourse-chat/lib/onebox/templates/discourse_chat.mustache" - File.read(path) - end + @onebox_template ||= + begin + path = "#{Rails.root}/plugins/discourse-chat/lib/onebox/templates/discourse_chat.mustache" + File.read(path) + end end end register_seedfu_fixtures(Rails.root.join("plugins", "discourse-chat", "db", "fixtures")) - load File.expand_path('../app/controllers/admin/admin_incoming_chat_webhooks_controller.rb', __FILE__) - load File.expand_path('../app/controllers/chat_base_controller.rb', __FILE__) - load File.expand_path('../app/controllers/chat_controller.rb', __FILE__) - load File.expand_path('../app/controllers/chat_channels_controller.rb', __FILE__) - load File.expand_path('../app/controllers/direct_messages_controller.rb', __FILE__) - load File.expand_path('../app/controllers/incoming_chat_webhooks_controller.rb', __FILE__) - load File.expand_path('../app/models/user_chat_channel_membership.rb', __FILE__) - load File.expand_path('../app/models/chat_channel.rb', __FILE__) - load File.expand_path('../app/models/chat_channel_archive.rb', __FILE__) - load File.expand_path('../app/models/chat_draft.rb', __FILE__) - load File.expand_path('../app/models/chat_message.rb', __FILE__) - load File.expand_path('../app/models/chat_message_reaction.rb', __FILE__) - load File.expand_path('../app/models/chat_message_revision.rb', __FILE__) - load File.expand_path('../app/models/chat_mention.rb', __FILE__) - load File.expand_path('../app/models/chat_upload.rb', __FILE__) - load File.expand_path('../app/models/chat_webhook_event.rb', __FILE__) - load File.expand_path('../app/models/direct_message_channel.rb', __FILE__) - load File.expand_path('../app/models/direct_message_user.rb', __FILE__) - load File.expand_path('../app/models/incoming_chat_webhook.rb', __FILE__) - load File.expand_path('../app/models/reviewable_chat_message.rb', __FILE__) - load File.expand_path('../app/models/chat_view.rb', __FILE__) - load File.expand_path('../app/serializers/chat_webhook_event_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/chat_in_reply_to_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/chat_message_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/chat_channel_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/chat_channel_settings_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/chat_channel_index_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/chat_channel_search_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/chat_view_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/direct_message_channel_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/incoming_chat_webhook_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/admin_chat_index_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/user_chat_channel_membership_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/user_chat_message_bookmark_serializer.rb', __FILE__) - load File.expand_path('../app/serializers/reviewable_chat_message_serializer.rb', __FILE__) - load File.expand_path('../lib/chat_channel_fetcher.rb', __FILE__) - load File.expand_path('../lib/chat_mailer.rb', __FILE__) - load File.expand_path('../lib/chat_message_creator.rb', __FILE__) - load File.expand_path('../lib/chat_message_processor.rb', __FILE__) - load File.expand_path('../lib/chat_message_updater.rb', __FILE__) - load File.expand_path('../lib/chat_message_rate_limiter.rb', __FILE__) - load File.expand_path('../lib/chat_message_reactor.rb', __FILE__) - load File.expand_path('../lib/chat_notifier.rb', __FILE__) - load File.expand_path('../lib/chat_seeder.rb', __FILE__) - load File.expand_path('../lib/chat_statistics.rb', __FILE__) - load File.expand_path('../lib/chat_transcript_service.rb', __FILE__) - load File.expand_path('../lib/duplicate_message_validator.rb', __FILE__) - load File.expand_path('../lib/message_mover.rb', __FILE__) - load File.expand_path('../lib/chat_message_bookmarkable.rb', __FILE__) - load File.expand_path('../lib/chat_channel_archive_service.rb', __FILE__) - load File.expand_path('../lib/direct_message_channel_creator.rb', __FILE__) - load File.expand_path('../lib/guardian_extensions.rb', __FILE__) - load File.expand_path('../lib/extensions/user_option_extension.rb', __FILE__) - load File.expand_path('../lib/extensions/user_notifications_extension.rb', __FILE__) - load File.expand_path('../lib/extensions/user_email_extension.rb', __FILE__) - load File.expand_path('../lib/slack_compatibility.rb', __FILE__) - load File.expand_path('../lib/post_notification_handler.rb', __FILE__) - load File.expand_path('../app/jobs/regular/auto_manage_channel_memberships.rb', __FILE__) - load File.expand_path('../app/jobs/regular/auto_join_channel_batch.rb', __FILE__) - load File.expand_path('../app/jobs/regular/process_chat_message.rb', __FILE__) - load File.expand_path('../app/jobs/regular/chat_channel_archive.rb', __FILE__) - load File.expand_path('../app/jobs/regular/chat_channel_delete.rb', __FILE__) - load File.expand_path('../app/jobs/regular/chat_notify_mentioned.rb', __FILE__) - load File.expand_path('../app/jobs/regular/chat_notify_watching.rb', __FILE__) - load File.expand_path('../app/jobs/scheduled/delete_old_chat_messages.rb', __FILE__) - load File.expand_path('../app/jobs/scheduled/update_user_counts_for_chat_channels.rb', __FILE__) - load File.expand_path('../app/jobs/scheduled/email_chat_notifications.rb', __FILE__) - load File.expand_path('../app/services/chat_publisher.rb', __FILE__) - load File.expand_path('../app/controllers/api_controller.rb', __FILE__) - load File.expand_path('../app/controllers/api/chat_channels_controller.rb', __FILE__) - load File.expand_path('../app/controllers/api/chat_channel_memberships_controller.rb', __FILE__) - load File.expand_path('../app/controllers/api/chat_channel_notifications_settings_controller.rb', __FILE__) - load File.expand_path('../app/controllers/api/category_chatables_controller.rb', __FILE__) - load File.expand_path('../app/queries/chat_channel_memberships_query.rb', __FILE__) + load File.expand_path( + "../app/controllers/admin/admin_incoming_chat_webhooks_controller.rb", + __FILE__, + ) + load File.expand_path("../app/controllers/chat_base_controller.rb", __FILE__) + load File.expand_path("../app/controllers/chat_controller.rb", __FILE__) + load File.expand_path("../app/controllers/chat_channels_controller.rb", __FILE__) + load File.expand_path("../app/controllers/direct_messages_controller.rb", __FILE__) + load File.expand_path("../app/controllers/incoming_chat_webhooks_controller.rb", __FILE__) + load File.expand_path("../app/models/user_chat_channel_membership.rb", __FILE__) + load File.expand_path("../app/models/chat_channel.rb", __FILE__) + load File.expand_path("../app/models/chat_channel_archive.rb", __FILE__) + load File.expand_path("../app/models/chat_draft.rb", __FILE__) + load File.expand_path("../app/models/chat_message.rb", __FILE__) + load File.expand_path("../app/models/chat_message_reaction.rb", __FILE__) + load File.expand_path("../app/models/chat_message_revision.rb", __FILE__) + load File.expand_path("../app/models/chat_mention.rb", __FILE__) + load File.expand_path("../app/models/chat_upload.rb", __FILE__) + load File.expand_path("../app/models/chat_webhook_event.rb", __FILE__) + load File.expand_path("../app/models/direct_message_channel.rb", __FILE__) + load File.expand_path("../app/models/direct_message_user.rb", __FILE__) + load File.expand_path("../app/models/incoming_chat_webhook.rb", __FILE__) + load File.expand_path("../app/models/reviewable_chat_message.rb", __FILE__) + load File.expand_path("../app/models/chat_view.rb", __FILE__) + load File.expand_path("../app/serializers/chat_webhook_event_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/chat_in_reply_to_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/chat_message_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/chat_channel_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/chat_channel_settings_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/chat_channel_index_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/chat_channel_search_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/chat_view_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/direct_message_channel_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/incoming_chat_webhook_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/admin_chat_index_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/user_chat_channel_membership_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/user_chat_message_bookmark_serializer.rb", __FILE__) + load File.expand_path("../app/serializers/reviewable_chat_message_serializer.rb", __FILE__) + load File.expand_path("../lib/chat_channel_fetcher.rb", __FILE__) + load File.expand_path("../lib/chat_mailer.rb", __FILE__) + load File.expand_path("../lib/chat_message_creator.rb", __FILE__) + load File.expand_path("../lib/chat_message_processor.rb", __FILE__) + load File.expand_path("../lib/chat_message_updater.rb", __FILE__) + load File.expand_path("../lib/chat_message_rate_limiter.rb", __FILE__) + load File.expand_path("../lib/chat_message_reactor.rb", __FILE__) + load File.expand_path("../lib/chat_notifier.rb", __FILE__) + load File.expand_path("../lib/chat_seeder.rb", __FILE__) + load File.expand_path("../lib/chat_statistics.rb", __FILE__) + load File.expand_path("../lib/chat_transcript_service.rb", __FILE__) + load File.expand_path("../lib/duplicate_message_validator.rb", __FILE__) + load File.expand_path("../lib/message_mover.rb", __FILE__) + load File.expand_path("../lib/chat_message_bookmarkable.rb", __FILE__) + load File.expand_path("../lib/chat_channel_archive_service.rb", __FILE__) + load File.expand_path("../lib/direct_message_channel_creator.rb", __FILE__) + load File.expand_path("../lib/guardian_extensions.rb", __FILE__) + load File.expand_path("../lib/extensions/user_option_extension.rb", __FILE__) + load File.expand_path("../lib/extensions/user_notifications_extension.rb", __FILE__) + load File.expand_path("../lib/extensions/user_email_extension.rb", __FILE__) + load File.expand_path("../lib/slack_compatibility.rb", __FILE__) + load File.expand_path("../lib/post_notification_handler.rb", __FILE__) + load File.expand_path("../app/jobs/regular/auto_manage_channel_memberships.rb", __FILE__) + load File.expand_path("../app/jobs/regular/auto_join_channel_batch.rb", __FILE__) + load File.expand_path("../app/jobs/regular/process_chat_message.rb", __FILE__) + load File.expand_path("../app/jobs/regular/chat_channel_archive.rb", __FILE__) + load File.expand_path("../app/jobs/regular/chat_channel_delete.rb", __FILE__) + load File.expand_path("../app/jobs/regular/chat_notify_mentioned.rb", __FILE__) + load File.expand_path("../app/jobs/regular/chat_notify_watching.rb", __FILE__) + load File.expand_path("../app/jobs/scheduled/delete_old_chat_messages.rb", __FILE__) + load File.expand_path("../app/jobs/scheduled/update_user_counts_for_chat_channels.rb", __FILE__) + load File.expand_path("../app/jobs/scheduled/email_chat_notifications.rb", __FILE__) + load File.expand_path("../app/services/chat_publisher.rb", __FILE__) + load File.expand_path("../app/controllers/api_controller.rb", __FILE__) + load File.expand_path("../app/controllers/api/chat_channels_controller.rb", __FILE__) + load File.expand_path("../app/controllers/api/chat_channel_memberships_controller.rb", __FILE__) + load File.expand_path( + "../app/controllers/api/chat_channel_notifications_settings_controller.rb", + __FILE__, + ) + load File.expand_path("../app/controllers/api/category_chatables_controller.rb", __FILE__) + load File.expand_path("../app/queries/chat_channel_memberships_query.rb", __FILE__) if Discourse.allow_dev_populate? - load File.expand_path('../lib/discourse_dev/public_channel.rb', __FILE__) - load File.expand_path('../lib/discourse_dev/direct_channel.rb', __FILE__) - load File.expand_path('../lib/discourse_dev/message.rb', __FILE__) + load File.expand_path("../lib/discourse_dev/public_channel.rb", __FILE__) + load File.expand_path("../lib/discourse_dev/direct_channel.rb", __FILE__) + load File.expand_path("../lib/discourse_dev/message.rb", __FILE__) end - UserNotifications.append_view_path(File.expand_path('../app/views', __FILE__)) + UserNotifications.append_view_path(File.expand_path("../app/views", __FILE__)) register_category_custom_field_type(DiscourseChat::HAS_CHAT_ENABLED, :boolean) @@ -195,130 +202,153 @@ def self.onebox_template Site.preloaded_category_custom_fields << DiscourseChat::HAS_CHAT_ENABLED Site.markdown_additional_options["chat"] = { limited_pretty_text_features: ChatMessage::MARKDOWN_FEATURES, - limited_pretty_text_markdown_rules: ChatMessage::MARKDOWN_IT_RULES + limited_pretty_text_markdown_rules: ChatMessage::MARKDOWN_IT_RULES, } Guardian.class_eval { include DiscourseChat::GuardianExtensions } UserNotifications.class_eval { prepend DiscourseChat::UserNotificationsExtension } UserOption.class_eval { prepend DiscourseChat::UserOptionExtension } - Category.class_eval { - has_one :chat_channel, as: :chatable - } - User.class_eval { + Category.class_eval { has_one :chat_channel, as: :chatable } + User.class_eval do has_many :user_chat_channel_memberships, dependent: :destroy has_many :chat_message_reactions, dependent: :destroy has_many :chat_mentions - } + end Jobs::UserEmail.class_eval { prepend DiscourseChat::UserEmailExtension } Bookmark.register_bookmarkable(ChatMessageBookmarkable) end - Oneboxer.register_local_handler('discourse_chat/chat') do |url, route| - queryParams = CGI.parse(URI.parse(url).query) rescue {} - messageId = queryParams['messageId']&.first - - if messageId.present? - message = ChatMessage.find_by(id: messageId) - next if !message - - chat_channel = message.chat_channel - user = message.user - next if !chat_channel || !user - else - chat_channel = ChatChannel.find_by(id: route[:channel_id]) - next if !chat_channel - end - - next if !Guardian.new.can_see_chat_channel?(chat_channel) - - name = if chat_channel.name.present? - chat_channel.name - end - - users = chat_channel - .user_chat_channel_memberships - .includes(:user).where(user: User.activated.not_suspended.not_staged) - .limit(10) - .map do |membership| - { - username: membership.user.username, - avatar_url: membership.user.avatar_template_url.gsub('{size}', '60'), - } + if Oneboxer.respond_to?(:register_local_handler) + Oneboxer.register_local_handler("discourse_chat/chat") do |url, route| + queryParams = + begin + CGI.parse(URI.parse(url).query) + rescue StandardError + {} + end + messageId = queryParams["messageId"]&.first + + if messageId.present? + message = ChatMessage.find_by(id: messageId) + next if !message + + chat_channel = message.chat_channel + user = message.user + next if !chat_channel || !user + else + chat_channel = ChatChannel.find_by(id: route[:channel_id]) + next if !chat_channel end - remaining_user_count_str = if chat_channel.user_count > users.size - I18n.t('chat.onebox.and_x_others', count: chat_channel.user_count - users.size) - end + next if !Guardian.new.can_see_chat_channel?(chat_channel) + + name = (chat_channel.name if chat_channel.name.present?) + + users = + chat_channel + .user_chat_channel_memberships + .includes(:user) + .where(user: User.activated.not_suspended.not_staged) + .limit(10) + .map do |membership| + { + username: membership.user.username, + avatar_url: membership.user.avatar_template_url.gsub("{size}", "60"), + } + end + + remaining_user_count_str = + if chat_channel.user_count > users.size + I18n.t("chat.onebox.and_x_others", count: chat_channel.user_count - users.size) + end - args = { - url: url, - channel_id: chat_channel.id, - channel_name: name, - description: chat_channel.description, - user_count_str: I18n.t('chat.onebox.x_members', count: chat_channel.user_count), - users: users, - remaining_user_count_str: remaining_user_count_str, - is_category: chat_channel.chatable_type == 'Category', - color: chat_channel.chatable_type == 'Category' ? chat_channel.chatable.color : nil, - } + args = { + url: url, + channel_id: chat_channel.id, + channel_name: name, + description: chat_channel.description, + user_count_str: I18n.t("chat.onebox.x_members", count: chat_channel.user_count), + users: users, + remaining_user_count_str: remaining_user_count_str, + is_category: chat_channel.chatable_type == "Category", + color: chat_channel.chatable_type == "Category" ? chat_channel.chatable.color : nil, + } + + if message.present? + args[:message_id] = message.id + args[:username] = message.user.username + args[:avatar_url] = message.user.avatar_template_url.gsub("{size}", "20") + args[:cooked] = message.cooked + args[:created_at] = message.created_at + args[:created_at_str] = message.created_at.iso8601 + end - if message.present? - args[:message_id] = message.id - args[:username] = message.user.username - args[:avatar_url] = message.user.avatar_template_url.gsub('{size}', '20') - args[:cooked] = message.cooked - args[:created_at] = message.created_at - args[:created_at_str] = message.created_at.iso8601 + Mustache.render(DiscourseChat.onebox_template, args) end + end - Mustache.render(DiscourseChat.onebox_template, args) - end if Oneboxer.respond_to?(:register_local_handler) - - InlineOneboxer.register_local_handler('discourse_chat/chat') do |url, route| - queryParams = CGI.parse(URI.parse(url).query) rescue {} - messageId = queryParams['messageId']&.first - - if messageId.present? - message = ChatMessage.find_by(id: messageId) - next if !message - - chat_channel = message.chat_channel - user = message.user - next if !chat_channel || !user - - title = I18n.t( - 'chat.onebox.inline_to_message', - message_id: message.id, - chat_channel: chat_channel.name, - username: user.username - ) - else - chat_channel = ChatChannel.find_by(id: route[:channel_id]) - next if !chat_channel - - title = if chat_channel.name.present? - I18n.t('chat.onebox.inline_to_channel', chat_channel: chat_channel.name) + if InlineOneboxer.respond_to?(:register_local_handler) + InlineOneboxer.register_local_handler("discourse_chat/chat") do |url, route| + queryParams = + begin + CGI.parse(URI.parse(url).query) + rescue StandardError + {} + end + messageId = queryParams["messageId"]&.first + + if messageId.present? + message = ChatMessage.find_by(id: messageId) + next if !message + + chat_channel = message.chat_channel + user = message.user + next if !chat_channel || !user + + title = + I18n.t( + "chat.onebox.inline_to_message", + message_id: message.id, + chat_channel: chat_channel.name, + username: user.username, + ) + else + chat_channel = ChatChannel.find_by(id: route[:channel_id]) + next if !chat_channel + + title = + if chat_channel.name.present? + I18n.t("chat.onebox.inline_to_channel", chat_channel: chat_channel.name) + end end - end - next if !Guardian.new.can_see_chat_channel?(chat_channel) + next if !Guardian.new.can_see_chat_channel?(chat_channel) - { url: url, title: title } - end if InlineOneboxer.respond_to?(:register_local_handler) + { url: url, title: title } + end + end if respond_to?(:register_upload_unused) register_upload_unused do |uploads| - uploads - .joins("LEFT JOIN chat_uploads cu ON cu.upload_id = uploads.id") - .where("cu.upload_id IS NULL") + uploads.joins("LEFT JOIN chat_uploads cu ON cu.upload_id = uploads.id").where( + "cu.upload_id IS NULL", + ) end end if respond_to?(:register_upload_in_use) register_upload_in_use do |upload| - ChatMessage.where("message LIKE ? OR message LIKE ?", "%#{upload.sha1}%", "%#{upload.base62_sha1}%").exists? || - ChatDraft.where("data LIKE ? OR data LIKE ?", "%#{upload.sha1}%", "%#{upload.base62_sha1}%").exists? + ChatMessage.where( + "message LIKE ? OR message LIKE ?", + "%#{upload.sha1}%", + "%#{upload.base62_sha1}%", + ).exists? || + ChatDraft.where( + "data LIKE ? OR data LIKE ?", + "%#{upload.sha1}%", + "%#{upload.base62_sha1}%", + ).exists? end end @@ -326,14 +356,10 @@ def self.onebox_template return false if !SiteSetting.chat_enabled return false if scope.user.blank? - scope.user.id != object.id && - scope.can_chat?(scope.user) && - scope.can_chat?(object) + scope.user.id != object.id && scope.can_chat?(scope.user) && scope.can_chat?(object) end - add_to_serializer(:current_user, :can_chat) do - true - end + add_to_serializer(:current_user, :can_chat) { true } add_to_serializer(:current_user, :include_can_chat?) do return @can_chat if defined?(@can_chat) @@ -341,9 +367,7 @@ def self.onebox_template @can_chat = SiteSetting.chat_enabled && scope.can_chat?(object) end - add_to_serializer(:current_user, :has_chat_enabled) do - true - end + add_to_serializer(:current_user, :has_chat_enabled) { true } add_to_serializer(:current_user, :include_has_chat_enabled?) do return @has_chat_enabled if defined?(@has_chat_enabled) @@ -351,33 +375,24 @@ def self.onebox_template @has_chat_enabled = include_can_chat? && object.user_option.chat_enabled end - add_to_serializer(:current_user, :chat_sound) do - object.user_option.chat_sound - end + add_to_serializer(:current_user, :chat_sound) { object.user_option.chat_sound } add_to_serializer(:current_user, :include_chat_sound?) do include_has_chat_enabled? && object.user_option.chat_sound end - add_to_serializer(:current_user, :needs_channel_retention_reminder) do - true - end + add_to_serializer(:current_user, :needs_channel_retention_reminder) { true } - add_to_serializer(:current_user, :needs_dm_retention_reminder) do - true - end + add_to_serializer(:current_user, :needs_dm_retention_reminder) { true } add_to_serializer(:current_user, :include_needs_channel_retention_reminder?) do - include_has_chat_enabled? && - object.staff? && + include_has_chat_enabled? && object.staff? && !object.user_option.dismissed_channel_retention_reminder && !SiteSetting.chat_channel_retention_days.zero? - end add_to_serializer(:current_user, :include_needs_dm_retention_reminder?) do - include_has_chat_enabled? && - !object.user_option.dismissed_dm_retention_reminder && + include_has_chat_enabled? && !object.user_option.dismissed_dm_retention_reminder && !SiteSetting.chat_dm_retention_days.zero? end @@ -385,26 +400,16 @@ def self.onebox_template ChatDraft .where(user_id: object.id) .pluck(:chat_channel_id, :data) - .map do |row| - { channel_id: row[0], data: row[1] } - end + .map { |row| { channel_id: row[0], data: row[1] } } end - add_to_serializer(:current_user, :include_chat_drafts?) do - include_has_chat_enabled? - end + add_to_serializer(:current_user, :include_chat_drafts?) { include_has_chat_enabled? } - add_to_serializer(:user_option, :chat_enabled) do - object.chat_enabled - end + add_to_serializer(:user_option, :chat_enabled) { object.chat_enabled } - add_to_serializer(:user_option, :chat_sound) do - object.chat_sound - end + add_to_serializer(:user_option, :chat_sound) { object.chat_sound } - add_to_serializer(:user_option, :include_chat_sound?) do - !object.chat_sound.blank? - end + add_to_serializer(:user_option, :include_chat_sound?) { !object.chat_sound.blank? } add_to_serializer(:user_option, :only_chat_push_notifications) do object.only_chat_push_notifications @@ -414,13 +419,11 @@ def self.onebox_template object.ignore_channel_wide_mention end - add_to_serializer(:user_option, :chat_email_frequency) do - object.chat_email_frequency - end + add_to_serializer(:user_option, :chat_email_frequency) { object.chat_email_frequency } RETENTION_SETTINGS_TO_USER_OPTION_FIELDS = { chat_channel_retention_days: :dismissed_channel_retention_reminder, - chat_dm_retention_days: :dismissed_dm_retention_reminder + chat_dm_retention_days: :dismissed_dm_retention_reminder, } on(:site_setting_changed) do |name, old_value, new_value| user_option_field = RETENTION_SETTINGS_TO_USER_OPTION_FIELDS[name.to_sym] @@ -429,7 +432,9 @@ def self.onebox_template UserOption.where(user_option_field => true).update_all(user_option_field => false) end rescue => e - Rails.logger.warn("Error updating user_options fields after chat retention settings changed: #{e}") + Rails.logger.warn( + "Error updating user_options fields after chat retention settings changed: #{e}", + ) end end @@ -446,14 +451,12 @@ def self.onebox_template end register_presence_channel_prefix("chat-reply") do |channel_name| - if chat_channel_id = channel_name[/\/chat-reply\/(\d+)/, 1] + if chat_channel_id = channel_name[%r{/chat-reply/(\d+)}, 1] chat_channel = ChatChannel.find(chat_channel_id) config = PresenceChannel::Config.new config.allowed_group_ids = chat_channel.allowed_group_ids config.allowed_user_ids = chat_channel.allowed_user_ids - if config.allowed_group_ids.nil? && config.allowed_user_ids.nil? - config.public = true - end + config.public = true if config.allowed_group_ids.nil? && config.allowed_user_ids.nil? config end rescue ActiveRecord::RecordNotFound @@ -461,20 +464,17 @@ def self.onebox_template end register_presence_channel_prefix("chat-user") do |channel_name| - if user_id = channel_name[/\/chat-user\/(chat|core)\/(\d+)/, 2] + if user_id = channel_name[%r{/chat-user/(chat|core)/(\d+)}, 2] user = User.find(user_id) config = PresenceChannel::Config.new - config.allowed_user_ids = [ user.id ] + config.allowed_user_ids = [user.id] config end rescue ActiveRecord::RecordNotFound nil end - CHAT_NOTIFICATION_TYPES = [ - Notification.types[:chat_mention], - Notification.types[:chat_message], - ] + CHAT_NOTIFICATION_TYPES = [Notification.types[:chat_mention], Notification.types[:chat_message]] register_push_notification_filter do |user, payload| if user.user_option.only_chat_push_notifications && user.user_option.chat_enabled CHAT_NOTIFICATION_TYPES.include?(payload[:notification_type]) @@ -483,30 +483,33 @@ def self.onebox_template end end - on(:reviewable_score_updated) do |reviewable| - ReviewableChatMessage.on_score_updated(reviewable) - end + on(:reviewable_score_updated) { |reviewable| ReviewableChatMessage.on_score_updated(reviewable) } on(:user_created) do |user| - ChatChannel.where(auto_join_users: true).each do |channel| - UserChatChannelMembership.enforce_automatic_user_membership(channel, user) - end + ChatChannel + .where(auto_join_users: true) + .each { |channel| UserChatChannelMembership.enforce_automatic_user_membership(channel, user) } end on(:user_confirmed_email) do |user| if user.active? - ChatChannel.where(auto_join_users: true).each do |channel| - UserChatChannelMembership.enforce_automatic_user_membership(channel, user) - end + ChatChannel + .where(auto_join_users: true) + .each do |channel| + UserChatChannelMembership.enforce_automatic_user_membership(channel, user) + end end end on(:user_added_to_group) do |user, group| - channels_to_add = ChatChannel - .distinct - .where(auto_join_users: true, chatable_type: 'Category') - .joins('INNER JOIN category_groups ON category_groups.category_id = chat_channels.chatable_id') - .where(category_groups: { group_id: group.id }) + channels_to_add = + ChatChannel + .distinct + .where(auto_join_users: true, chatable_type: "Category") + .joins( + "INNER JOIN category_groups ON category_groups.category_id = chat_channels.chatable_id", + ) + .where(category_groups: { group_id: group.id }) channels_to_add.each do |channel| UserChatChannelMembership.enforce_automatic_user_membership(channel, user) @@ -526,86 +529,99 @@ def self.onebox_template DiscourseChat::Engine.routes.draw do namespace :api do - get '/chat_channels' => 'chat_channels#index' - get '/chat_channels/:chat_channel_id/memberships' => 'chat_channel_memberships#index' - put '/chat_channels/:chat_channel_id' => 'chat_channels#update' - put '/chat_channels/:chat_channel_id/notifications_settings' => 'chat_channel_notifications_settings#update' + get "/chat_channels" => "chat_channels#index" + get "/chat_channels/:chat_channel_id/memberships" => "chat_channel_memberships#index" + put "/chat_channels/:chat_channel_id" => "chat_channels#update" + put "/chat_channels/:chat_channel_id/notifications_settings" => + "chat_channel_notifications_settings#update" # hints controller. Only used by staff members, we don't want to leak category permissions. - get '/category-chatables/:id/permissions' => 'category_chatables#permissions', format: :json, constraints: StaffConstraint.new + get "/category-chatables/:id/permissions" => "category_chatables#permissions", + :format => :json, + :constraints => StaffConstraint.new end # direct_messages_controller routes - get '/direct_messages' => 'direct_messages#index' - post '/direct_messages/create' => 'direct_messages#create' + get "/direct_messages" => "direct_messages#index" + post "/direct_messages/create" => "direct_messages#create" # incoming_webhooks_controller routes - post '/hooks/:key' => 'incoming_chat_webhooks#create_message' + post "/hooks/:key" => "incoming_chat_webhooks#create_message" # incoming_webhooks_controller routes - post '/hooks/:key/slack' => 'incoming_chat_webhooks#create_message_slack_compatible' + post "/hooks/:key/slack" => "incoming_chat_webhooks#create_message_slack_compatible" # chat_channel_controller routes - get '/chat_channels' => 'chat_channels#index' - put '/chat_channels' => 'chat_channels#create' - get '/chat_channels/search' => 'chat_channels#search' - post '/chat_channels/:chat_channel_id' => 'chat_channels#edit' - post '/chat_channels/:chat_channel_id/notification_settings' => 'chat_channels#notification_settings' - post '/chat_channels/:chat_channel_id/follow' => 'chat_channels#follow' - post '/chat_channels/:chat_channel_id/unfollow' => 'chat_channels#unfollow' - get '/chat_channels/:chat_channel_id' => 'chat_channels#show' - put '/chat_channels/:chat_channel_id/archive' => 'chat_channels#archive' - put '/chat_channels/:chat_channel_id/retry_archive' => 'chat_channels#retry_archive' - put '/chat_channels/:chat_channel_id/change_status' => 'chat_channels#change_status' - delete '/chat_channels/:chat_channel_id' => 'chat_channels#destroy' + get "/chat_channels" => "chat_channels#index" + put "/chat_channels" => "chat_channels#create" + get "/chat_channels/search" => "chat_channels#search" + post "/chat_channels/:chat_channel_id" => "chat_channels#edit" + post "/chat_channels/:chat_channel_id/notification_settings" => + "chat_channels#notification_settings" + post "/chat_channels/:chat_channel_id/follow" => "chat_channels#follow" + post "/chat_channels/:chat_channel_id/unfollow" => "chat_channels#unfollow" + get "/chat_channels/:chat_channel_id" => "chat_channels#show" + put "/chat_channels/:chat_channel_id/archive" => "chat_channels#archive" + put "/chat_channels/:chat_channel_id/retry_archive" => "chat_channels#retry_archive" + put "/chat_channels/:chat_channel_id/change_status" => "chat_channels#change_status" + delete "/chat_channels/:chat_channel_id" => "chat_channels#destroy" # chat_controller routes - get '/' => 'chat#respond' - get '/browse' => 'chat#respond' - get '/browse/all' => 'chat#respond' - get '/browse/closed' => 'chat#respond' - get '/browse/open' => 'chat#respond' - get '/browse/archived' => 'chat#respond' - get '/draft-channel' => 'chat#respond' - get '/channel/:channel_id' => 'chat#respond' - get '/channel/:channel_id/:channel_title' => 'chat#respond' - get '/channel/:channel_id/:channel_title/info' => 'chat#respond' - get '/channel/:channel_id/:channel_title/info/about' => 'chat#respond' - get '/channel/:channel_id/:channel_title/info/members' => 'chat#respond' - get '/channel/:channel_id/:channel_title/info/settings' => 'chat#respond' - post '/enable' => 'chat#enable_chat' - post '/disable' => 'chat#disable_chat' - post '/dismiss-retention-reminder' => 'chat#dismiss_retention_reminder' - get '/:chat_channel_id/messages' => 'chat#messages' - get '/message/:message_id' => 'chat#message_link' - put ':chat_channel_id/edit/:message_id' => 'chat#edit_message' - put ':chat_channel_id/react/:message_id' => 'chat#react' - delete '/:chat_channel_id/:message_id' => 'chat#delete' - put '/:chat_channel_id/:message_id/rebake' => 'chat#rebake' - post '/:chat_channel_id/:message_id/flag' => 'chat#flag' - post '/:chat_channel_id/quote' => 'chat#quote_messages' - put '/:chat_channel_id/move_messages_to_channel' => 'chat#move_messages_to_channel' - put '/:chat_channel_id/restore/:message_id' => 'chat#restore' - get '/lookup/:message_id' => 'chat#lookup_message' - put '/:chat_channel_id/read/:message_id' => 'chat#update_user_last_read' - put '/user_chat_enabled/:user_id' => 'chat#set_user_chat_status' - put '/:chat_channel_id/invite' => 'chat#invite_users' - post '/drafts' => 'chat#set_draft' - post '/:chat_channel_id' => 'chat#create_message' - put '/flag' => 'chat#flag' + get "/" => "chat#respond" + get "/browse" => "chat#respond" + get "/browse/all" => "chat#respond" + get "/browse/closed" => "chat#respond" + get "/browse/open" => "chat#respond" + get "/browse/archived" => "chat#respond" + get "/draft-channel" => "chat#respond" + get "/channel/:channel_id" => "chat#respond" + get "/channel/:channel_id/:channel_title" => "chat#respond" + get "/channel/:channel_id/:channel_title/info" => "chat#respond" + get "/channel/:channel_id/:channel_title/info/about" => "chat#respond" + get "/channel/:channel_id/:channel_title/info/members" => "chat#respond" + get "/channel/:channel_id/:channel_title/info/settings" => "chat#respond" + post "/enable" => "chat#enable_chat" + post "/disable" => "chat#disable_chat" + post "/dismiss-retention-reminder" => "chat#dismiss_retention_reminder" + get "/:chat_channel_id/messages" => "chat#messages" + get "/message/:message_id" => "chat#message_link" + put ":chat_channel_id/edit/:message_id" => "chat#edit_message" + put ":chat_channel_id/react/:message_id" => "chat#react" + delete "/:chat_channel_id/:message_id" => "chat#delete" + put "/:chat_channel_id/:message_id/rebake" => "chat#rebake" + post "/:chat_channel_id/:message_id/flag" => "chat#flag" + post "/:chat_channel_id/quote" => "chat#quote_messages" + put "/:chat_channel_id/move_messages_to_channel" => "chat#move_messages_to_channel" + put "/:chat_channel_id/restore/:message_id" => "chat#restore" + get "/lookup/:message_id" => "chat#lookup_message" + put "/:chat_channel_id/read/:message_id" => "chat#update_user_last_read" + put "/user_chat_enabled/:user_id" => "chat#set_user_chat_status" + put "/:chat_channel_id/invite" => "chat#invite_users" + post "/drafts" => "chat#set_draft" + post "/:chat_channel_id" => "chat#create_message" + put "/flag" => "chat#flag" end Discourse::Application.routes.append do - mount ::DiscourseChat::Engine, at: '/chat' - get '/admin/plugins/chat' => 'discourse_chat/admin_incoming_chat_webhooks#index', constraints: StaffConstraint.new - post '/admin/plugins/chat/hooks' => 'discourse_chat/admin_incoming_chat_webhooks#create', constraints: StaffConstraint.new - put '/admin/plugins/chat/hooks/:incoming_chat_webhook_id' => 'discourse_chat/admin_incoming_chat_webhooks#update', constraints: StaffConstraint.new - delete '/admin/plugins/chat/hooks/:incoming_chat_webhook_id' => 'discourse_chat/admin_incoming_chat_webhooks#destroy', constraints: StaffConstraint.new - get "u/:username/preferences/chat" => "users#preferences", constraints: { username: RouteFormat.username } + mount ::DiscourseChat::Engine, at: "/chat" + get "/admin/plugins/chat" => "discourse_chat/admin_incoming_chat_webhooks#index", + :constraints => StaffConstraint.new + post "/admin/plugins/chat/hooks" => "discourse_chat/admin_incoming_chat_webhooks#create", + :constraints => StaffConstraint.new + put "/admin/plugins/chat/hooks/:incoming_chat_webhook_id" => + "discourse_chat/admin_incoming_chat_webhooks#update", + :constraints => StaffConstraint.new + delete "/admin/plugins/chat/hooks/:incoming_chat_webhook_id" => + "discourse_chat/admin_incoming_chat_webhooks#destroy", + :constraints => StaffConstraint.new + get "u/:username/preferences/chat" => "users#preferences", + :constraints => { + username: RouteFormat.username, + } end if defined?(DiscourseAutomation) - add_automation_scriptable('send_chat_message') do + add_automation_scriptable("send_chat_message") do field :chat_channel_id, component: :text, required: true field :message, component: :message, required: true, accepts_placeholders: true field :sender, component: :user @@ -613,19 +629,20 @@ def self.onebox_template placeholder :channel_name script do |context, fields, automation| - sender = User.find_by(username: fields.dig('sender', 'value')) || Discourse.system_user - channel = ChatChannel.find_by(id: fields.dig('chat_channel_id', 'value')) - - placeholders = { - channel_name: channel.public_channel_title - }.merge(context['placeholders'] || {}) + sender = User.find_by(username: fields.dig("sender", "value")) || Discourse.system_user + channel = ChatChannel.find_by(id: fields.dig("chat_channel_id", "value")) - creator = DiscourseChat::ChatMessageCreator.create( - chat_channel: channel, - user: sender, - content: utils.apply_placeholders(fields.dig('message', 'value'), placeholders) + placeholders = { channel_name: channel.public_channel_title }.merge( + context["placeholders"] || {}, ) + creator = + DiscourseChat::ChatMessageCreator.create( + chat_channel: channel, + user: sender, + content: utils.apply_placeholders(fields.dig("message", "value"), placeholders), + ) + if creator.failed? Rails.logger.warn "[discourse-automation] Chat message failed to send, error was: #{creator.error}" end @@ -633,40 +650,37 @@ def self.onebox_template end end - add_api_key_scope(:chat, { - create_message: { - actions: %w[discourse_chat/chat#create_message], - params: %i[chat_channel_id] - } - }) + add_api_key_scope( + :chat, + { + create_message: { + actions: %w[discourse_chat/chat#create_message], + params: %i[chat_channel_id], + }, + }, + ) # Dark mode email styles Email::Styles.register_plugin_style do |fragment| - fragment.css('.chat-summary-header').each { |element| element[:dm] = 'header' } - fragment.css('.chat-summary-content').each { |element| element[:dm] = 'body' } + fragment.css(".chat-summary-header").each { |element| element[:dm] = "header" } + fragment.css(".chat-summary-content").each { |element| element[:dm] = "body" } end # TODO(roman): Remove `respond_to?` after 2.9 release if respond_to?(:register_email_unsubscriber) - load File.expand_path('../lib/email_controller_helper/chat_summary_unsubscriber.rb', __FILE__) - register_email_unsubscriber('chat_summary', EmailControllerHelper::ChatSummaryUnsubscriber) + load File.expand_path("../lib/email_controller_helper/chat_summary_unsubscriber.rb", __FILE__) + register_email_unsubscriber("chat_summary", EmailControllerHelper::ChatSummaryUnsubscriber) end register_about_stat_group("chat_messages", show_in_ui: true) do DiscourseChat::Statistics.about_messages end - register_about_stat_group("chat_channels") do - DiscourseChat::Statistics.about_channels - end + register_about_stat_group("chat_channels") { DiscourseChat::Statistics.about_channels } - register_about_stat_group("chat_users") do - DiscourseChat::Statistics.about_users - end + register_about_stat_group("chat_users") { DiscourseChat::Statistics.about_users } end -if Rails.env == 'test' - Dir[Rails.root.join("plugins/discourse-chat/spec/support/**/*.rb")].each do |f| - require f - end +if Rails.env == "test" + Dir[Rails.root.join("plugins/discourse-chat/spec/support/**/*.rb")].each { |f| require f } end diff --git a/spec/components/chat_mailer_spec.rb b/spec/components/chat_mailer_spec.rb index 51de3c9c5..17a6aaffd 100644 --- a/spec/components/chat_mailer_spec.rb +++ b/spec/components/chat_mailer_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" describe DiscourseChat::ChatMailer do before do @@ -17,16 +17,22 @@ Fabricate(:user_chat_channel_membership, user: @sender, chat_channel: @chat_channel) - @user_membership = Fabricate( - :user_chat_channel_membership, user: @user_1, - chat_channel: @chat_channel, last_read_message_id: nil - ) + @user_membership = + Fabricate( + :user_chat_channel_membership, + user: @user_1, + chat_channel: @chat_channel, + last_read_message_id: nil, + ) - @private_channel = DiscourseChat::DirectMessageChannelCreator.create!(target_users: [@sender, @user_1]) + @private_channel = + DiscourseChat::DirectMessageChannelCreator.create!(target_users: [@sender, @user_1]) end def assert_summary_skipped - expect(job_enqueued?(job: :user_email, args: { type: "chat_summary", user_id: @user_1.id })).to eq(false) + expect( + job_enqueued?(job: :user_email, args: { type: "chat_summary", user_id: @user_1.id }), + ).to eq(false) end def assert_only_queued_once @@ -34,12 +40,10 @@ def assert_only_queued_once expect(Jobs::UserEmail.jobs.size).to eq(1) end - describe 'for chat mentions' do - before do - @mention = Fabricate(:chat_mention, user: @user_1, chat_message: @chat_message) - end + describe "for chat mentions" do + before { @mention = Fabricate(:chat_mention, user: @user_1, chat_message: @chat_message) } - it 'skips users without chat access' do + it "skips users without chat access" do @chatters_group.remove(@user_1) described_class.send_unread_mentions_summary @@ -47,7 +51,7 @@ def assert_only_queued_once assert_summary_skipped end - it 'skips users with summaries disabled' do + it "skips users with summaries disabled" do @user_1.user_option.update(chat_email_frequency: UserOption.chat_email_frequencies[:never]) described_class.send_unread_mentions_summary @@ -63,21 +67,24 @@ def assert_only_queued_once assert_summary_skipped end - it 'skips without chat enabled' do - @user_1.user_option.update(chat_enabled: false, chat_email_frequency: UserOption.chat_email_frequencies[:when_away]) + it "skips without chat enabled" do + @user_1.user_option.update( + chat_enabled: false, + chat_email_frequency: UserOption.chat_email_frequencies[:when_away], + ) described_class.send_unread_mentions_summary assert_summary_skipped end - it 'queues a job for users that was mentioned and never read the channel before' do + it "queues a job for users that was mentioned and never read the channel before" do described_class.send_unread_mentions_summary assert_only_queued_once end - it 'skips the job when the user was mentioned but already read the message' do + it "skips the job when the user was mentioned but already read the message" do @user_membership.update!(last_read_message_id: @chat_message.id) described_class.send_unread_mentions_summary @@ -85,7 +92,7 @@ def assert_only_queued_once assert_summary_skipped end - it 'skips the job when the user is not following the channel anymore' do + it "skips the job when the user is not following the channel anymore" do @user_membership.update!(last_read_message_id: @chat_message.id - 1, following: false) described_class.send_unread_mentions_summary @@ -93,26 +100,31 @@ def assert_only_queued_once assert_summary_skipped end - it 'skips users with unread messages from a different channel' do + it "skips users with unread messages from a different channel" do @user_membership.update!(last_read_message_id: @chat_message.id) second_channel = Fabricate(:chat_channel) - Fabricate(:user_chat_channel_membership, user: @user_1, chat_channel: second_channel, last_read_message_id: @chat_message.id - 1) + Fabricate( + :user_chat_channel_membership, + user: @user_1, + chat_channel: second_channel, + last_read_message_id: @chat_message.id - 1, + ) described_class.send_unread_mentions_summary assert_summary_skipped end - it 'only queues the job once for users who are member of multiple groups with chat access' do + it "only queues the job once for users who are member of multiple groups with chat access" do chatters_group_2 = Fabricate(:group, users: [@user_1]) - SiteSetting.chat_allowed_groups = [@chatters_group, chatters_group_2].map(&:id).join('|') + SiteSetting.chat_allowed_groups = [@chatters_group, chatters_group_2].map(&:id).join("|") described_class.send_unread_mentions_summary assert_only_queued_once end - it 'skips users when the mention was deleted' do + it "skips users when the mention was deleted" do @chat_message.trash! described_class.send_unread_mentions_summary @@ -120,8 +132,11 @@ def assert_only_queued_once assert_summary_skipped end - it 'queues the job if the user has unread mentions and already read all the messages in the previous summary' do - @user_membership.update!(last_read_message_id: @chat_message.id, last_unread_mention_when_emailed_id: @chat_message.id) + it "queues the job if the user has unread mentions and already read all the messages in the previous summary" do + @user_membership.update!( + last_read_message_id: @chat_message.id, + last_unread_mention_when_emailed_id: @chat_message.id, + ) unread_message = Fabricate(:chat_message, chat_channel: @chat_channel, user: @sender) Fabricate(:chat_mention, user: @user_1, chat_message: unread_message) @@ -131,7 +146,7 @@ def assert_only_queued_once expect(Jobs::UserEmail.jobs.size).to eq(1) end - it 'skips users who were seen recently' do + it "skips users who were seen recently" do @user_1.update!(last_seen_at: 2.minutes.ago) described_class.send_unread_mentions_summary @@ -142,18 +157,26 @@ def assert_only_queued_once it "doesn't mix mentions from other users" do @mention.destroy! user_2 = Fabricate(:user, groups: [@chatters_group], last_seen_at: 20.minutes.ago) - user_2_membership = Fabricate(:user_chat_channel_membership, user: user_2, chat_channel: @chat_channel, last_read_message_id: nil) + user_2_membership = + Fabricate( + :user_chat_channel_membership, + user: user_2, + chat_channel: @chat_channel, + last_read_message_id: nil, + ) new_message = Fabricate(:chat_message, chat_channel: @chat_channel, user: @sender) Fabricate(:chat_mention, user: user_2, chat_message: new_message) described_class.send_unread_mentions_summary - expect(job_enqueued?(job: :user_email, args: { type: "chat_summary", user_id: @user_1.id })).to eq(false) + expect( + job_enqueued?(job: :user_email, args: { type: "chat_summary", user_id: @user_1.id }), + ).to eq(false) expect_job_enqueued(job: :user_email, args: { type: "chat_summary", user_id: user_2.id }) expect(Jobs::UserEmail.jobs.size).to eq(1) end - it 'skips users when the message is older than 1 week' do + it "skips users when the message is older than 1 week" do @chat_message.update!(created_at: 1.5.weeks.ago) described_class.send_unread_mentions_summary @@ -161,7 +184,7 @@ def assert_only_queued_once assert_summary_skipped end - describe 'update the user membership after we send the email' do + describe "update the user membership after we send the email" do before { Jobs.run_immediately! } it "doesn't send the same summary the summary again if the user haven't read any channel messages since the last one" do @@ -170,20 +193,28 @@ def assert_only_queued_once expect(@user_membership.reload.last_unread_mention_when_emailed_id).to eq(@chat_message.id) - another_channel_message = Fabricate(:chat_message, chat_channel: @chat_channel, user: @sender) + another_channel_message = + Fabricate(:chat_message, chat_channel: @chat_channel, user: @sender) Fabricate(:chat_mention, user: @user_1, chat_message: another_channel_message) - expect { described_class.send_unread_mentions_summary }.to change(Jobs::UserEmail.jobs, :size).by(0) + expect { described_class.send_unread_mentions_summary }.to change( + Jobs::UserEmail.jobs, + :size, + ).by(0) end - it 'only updates the last_message_read_when_emailed_id on the channel with unread mentions' do + it "only updates the last_message_read_when_emailed_id on the channel with unread mentions" do another_channel = Fabricate(:chat_channel) - another_channel_message = Fabricate(:chat_message, chat_channel: another_channel, user: @sender) + another_channel_message = + Fabricate(:chat_message, chat_channel: another_channel, user: @sender) Fabricate(:chat_mention, user: @user_1, chat_message: another_channel_message) - another_channel_membership = Fabricate( - :user_chat_channel_membership, user: @user_1, chat_channel: another_channel, - last_read_message_id: another_channel_message.id - ) + another_channel_membership = + Fabricate( + :user_chat_channel_membership, + user: @user_1, + chat_channel: another_channel, + last_read_message_id: another_channel_message.id, + ) @user_membership.update!(last_read_message_id: @chat_message.id - 1) described_class.send_unread_mentions_summary @@ -194,18 +225,16 @@ def assert_only_queued_once end end - describe 'for direct messages' do - before do - Fabricate(:chat_message, user: @sender, chat_channel: @private_channel) - end + describe "for direct messages" do + before { Fabricate(:chat_message, user: @sender, chat_channel: @private_channel) } - it 'queue a job when the user has unread private mentions' do + it "queue a job when the user has unread private mentions" do described_class.send_unread_mentions_summary assert_only_queued_once end - it 'only queues the job once when the user has mentions and private messages' do + it "only queues the job once when the user has mentions and private messages" do Fabricate(:chat_mention, user: @user_1, chat_message: @chat_message) described_class.send_unread_mentions_summary @@ -215,7 +244,13 @@ def assert_only_queued_once it "Doesn't mix or update mentions from other users when joining tables" do user_2 = Fabricate(:user, groups: [@chatters_group], last_seen_at: 20.minutes.ago) - user_2_membership = Fabricate(:user_chat_channel_membership, user: user_2, chat_channel: @chat_channel, last_read_message_id: @chat_message.id) + user_2_membership = + Fabricate( + :user_chat_channel_membership, + user: user_2, + chat_channel: @chat_channel, + last_read_message_id: @chat_message.id, + ) Fabricate(:chat_mention, user: user_2, chat_message: @chat_message) described_class.send_unread_mentions_summary diff --git a/spec/components/chat_message_creator_spec.rb b/spec/components/chat_message_creator_spec.rb index 459dc34ab..3c50285ff 100644 --- a/spec/components/chat_message_creator_spec.rb +++ b/spec/components/chat_message_creator_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" describe DiscourseChat::ChatMessageCreator do fab!(:admin1) { Fabricate(:admin) } @@ -8,11 +8,28 @@ fab!(:user1) { Fabricate(:user, group_ids: [Group::AUTO_GROUPS[:everyone]]) } fab!(:user2) { Fabricate(:user) } fab!(:user3) { Fabricate(:user) } - fab!(:admin_group) { Fabricate(:public_group, users: [admin1, admin2], mentionable_level: Group::ALIAS_LEVELS[:everyone]) } - fab!(:user_group) { Fabricate(:public_group, users: [user1, user2, user3], mentionable_level: Group::ALIAS_LEVELS[:everyone]) } + fab!(:admin_group) do + Fabricate( + :public_group, + users: [admin1, admin2], + mentionable_level: Group::ALIAS_LEVELS[:everyone], + ) + end + fab!(:user_group) do + Fabricate( + :public_group, + users: [user1, user2, user3], + mentionable_level: Group::ALIAS_LEVELS[:everyone], + ) + end fab!(:user_without_memberships) { Fabricate(:user) } fab!(:public_chat_channel) { Fabricate(:chat_channel, chatable: Fabricate(:topic)) } - fab!(:dm_chat_channel) { Fabricate(:chat_channel, chatable: Fabricate(:direct_message_channel, users: [user1, user2, user3])) } + fab!(:dm_chat_channel) do + Fabricate( + :chat_channel, + chatable: Fabricate(:direct_message_channel, users: [user1, user2, user3]), + ) + end before do SiteSetting.chat_enabled = true @@ -24,23 +41,28 @@ Fabricate(:user_chat_channel_membership, chat_channel: public_chat_channel, user: user) end - @direct_message_channel = DiscourseChat::DirectMessageChannelCreator.create!(target_users: [user1, user2]) + @direct_message_channel = + DiscourseChat::DirectMessageChannelCreator.create!(target_users: [user1, user2]) end describe "Integration tests with jobs running immediately" do - before do - Jobs.run_immediately! - end + before { Jobs.run_immediately! } it "errors when length is less than `chat_minimum_message_length`" do SiteSetting.chat_minimum_message_length = 10 - creator = DiscourseChat::ChatMessageCreator.create( - chat_channel: public_chat_channel, - user: user1, - content: "2 short" - ) + creator = + DiscourseChat::ChatMessageCreator.create( + chat_channel: public_chat_channel, + user: user1, + content: "2 short", + ) expect(creator.failed?).to eq(true) - expect(creator.error.message).to match(I18n.t("chat.errors.minimum_length_not_met", { minimum: SiteSetting.chat_minimum_message_length })) + expect(creator.error.message).to match( + I18n.t( + "chat.errors.minimum_length_not_met", + { minimum: SiteSetting.chat_minimum_message_length }, + ), + ) end it "allows message creation when length is less than `chat_minimum_message_length` when upload is present" do @@ -51,7 +73,7 @@ chat_channel: public_chat_channel, user: user1, content: "2 short", - upload_ids: [upload.id] + upload_ids: [upload.id], ) }.to change { ChatMessage.count }.by(1) end @@ -61,7 +83,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "this is a message" + content: "this is a message", ) }.to change { ChatMessage.count }.by(1) end @@ -71,21 +93,20 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "this is a @#{user1.username} message with @system @mentions @#{user2.username} and @#{user3.username}" + content: + "this is a @#{user1.username} message with @system @mentions @#{user2.username} and @#{user3.username}", ) # Only 2 mentions are created because user mentioned themselves, system, and an invalid username. - }.to change { ChatMention.count }.by(2) - .and change { - user1.chat_mentions.count - }.by(0) + }.to change { ChatMention.count }.by(2).and change { user1.chat_mentions.count }.by(0) end it "mentions are case insensitive" do - expect { DiscourseChat::ChatMessageCreator.create( - chat_channel: public_chat_channel, - user: user1, - content: "Hey @#{user2.username.upcase}" - ) + expect { + DiscourseChat::ChatMessageCreator.create( + chat_channel: public_chat_channel, + user: user1, + content: "Hey @#{user2.username.upcase}", + ) }.to change { user2.chat_mentions.count }.by(1) end @@ -94,16 +115,18 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "@all" + content: "@all", ) }.to change { ChatMention.count }.by(4) - UserChatChannelMembership.where(user: user2, chat_channel: public_chat_channel).update_all(following: false) + UserChatChannelMembership.where(user: user2, chat_channel: public_chat_channel).update_all( + following: false, + ) expect { DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "again! @all" + content: "again! @all", ) }.to change { ChatMention.count }.by(3) end @@ -118,7 +141,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "@here" + content: "@here", ) }.to change { ChatMention.count }.by(2) end @@ -129,7 +152,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "@here @#{user2.username}" + content: "@here @#{user2.username}", ) }.to change { user2.chat_mentions.count }.by(1) end @@ -144,7 +167,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "@here plus @#{user3.username}" + content: "@here plus @#{user3.username}", ) }.to change { user3.chat_mentions.count }.by(1) end @@ -154,7 +177,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "hello @#{user_without_memberships.username}" + content: "hello @#{user_without_memberships.username}", ) }.to change { ChatMention.count }.by(0) end @@ -166,7 +189,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "hi @#{user2.username} @#{user3.username}" + content: "hi @#{user2.username} @#{user3.username}", ) }.to change { ChatMention.count }.by(0) end @@ -177,7 +200,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "hi @#{user2.username}" + content: "hi @#{user2.username}", ) }.to change { ChatMention.count }.by(0) end @@ -187,36 +210,33 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: @direct_message_channel, user: user1, - content: "hello there @#{user2.username} and @#{user3.username}" + content: "hello there @#{user2.username} and @#{user3.username}", ) # Only user2 should be notified - }.to change { user2.chat_mentions.count }.by(1) - .and change { - user3.chat_mentions.count - }.by(0) + }.to change { user2.chat_mentions.count }.by(1).and change { user3.chat_mentions.count }.by(0) end - it 'creates a mention notifications for group users that are participating in private chat' do + it "creates a mention notifications for group users that are participating in private chat" do expect { DiscourseChat::ChatMessageCreator.create( chat_channel: @direct_message_channel, user: user1, - content: "hello there @#{user_group.name}" + content: "hello there @#{user_group.name}", ) # Only user2 should be notified - }.to change { user2.chat_mentions.count }.by(1) - .and change { - user3.chat_mentions.count - }.by(0) + }.to change { user2.chat_mentions.count }.by(1).and change { user3.chat_mentions.count }.by(0) end it "publishes inaccessible mentions when user isn't aren't a part of the channel" do - user3.user_chat_channel_memberships.where(chat_channel: public_chat_channel).update(following: false) + user3 + .user_chat_channel_memberships + .where(chat_channel: public_chat_channel) + .update(following: false) ChatPublisher.expects(:publish_inaccessible_mentions).once DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: admin1, - content: "hello @#{user3.username}" + content: "hello @#{user3.username}", ) end @@ -226,7 +246,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: admin1, - content: "hello @#{user3.username}" + content: "hello @#{user3.username}", ) end @@ -235,7 +255,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: admin1, - content: "hello @#{admin2.username}" + content: "hello @#{admin2.username}", ) end @@ -245,7 +265,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: @direct_message_channel, user: user1, - content: "hello @#{user2.username}" + content: "hello @#{user2.username}", ) }.to change { user2.chat_mentions.count }.by(0) end @@ -255,7 +275,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "@all" + content: "@all", ) }.to change { ChatMention.count }.by(4) @@ -264,7 +284,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "hi! @all" + content: "hi! @all", ) }.to change { ChatMention.count }.by(3) end @@ -281,7 +301,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "@here" + content: "@here", ) }.to change { ChatMention.count }.by(1) end @@ -292,12 +312,11 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "hello @#{admin_group.name}" + content: "hello @#{admin_group.name}", ) - }.to change { admin1.chat_mentions.count }.by(1) - .and change { - admin2.chat_mentions.count - }.by(1) + }.to change { admin1.chat_mentions.count }.by(1).and change { + admin2.chat_mentions.count + }.by(1) end it "doesn't mention users twice if they are direct mentioned and group mentioned" do @@ -305,12 +324,11 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "hello @#{admin_group.name} @#{admin1.username} and @#{admin2.username}" + content: "hello @#{admin_group.name} @#{admin1.username} and @#{admin2.username}", ) - }.to change { admin1.chat_mentions.count }.by(1) - .and change { - admin2.chat_mentions.count - }.by(1) + }.to change { admin1.chat_mentions.count }.by(1).and change { + admin2.chat_mentions.count + }.by(1) end it "creates chat mentions for group mentions and direct mentions" do @@ -318,15 +336,11 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "hello @#{admin_group.name} @#{user2.username}" + content: "hello @#{admin_group.name} @#{user2.username}", ) - }.to change { admin1.chat_mentions.count }.by(1) - .and change { - admin2.chat_mentions.count - }.by(1) - .and change { - user2.chat_mentions.count - }.by(1) + }.to change { admin1.chat_mentions.count }.by(1).and change { + admin2.chat_mentions.count + }.by(1).and change { user2.chat_mentions.count }.by(1) end it "creates chat mentions for group mentions and direct mentions" do @@ -334,18 +348,13 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "hello @#{admin_group.name} @#{user_group.name}" + content: "hello @#{admin_group.name} @#{user_group.name}", ) - }.to change { admin1.chat_mentions.count }.by(1) - .and change { - admin2.chat_mentions.count - }.by(1) - .and change { - user2.chat_mentions.count - }.by(1) - .and change { - user3.chat_mentions.count - }.by(1) + }.to change { admin1.chat_mentions.count }.by(1).and change { + admin2.chat_mentions.count + }.by(1).and change { user2.chat_mentions.count }.by(1).and change { + user3.chat_mentions.count + }.by(1) end it "doesn't create chat mentions for group mentions where the group is un-mentionable" do @@ -354,7 +363,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "hello @#{admin_group.name}" + content: "hello @#{admin_group.name}", ) }.to change { ChatMention.count }.by(0) end @@ -362,9 +371,9 @@ describe "push notifications" do before do - UserChatChannelMembership - .where(user: user1, chat_channel: public_chat_channel) - .update(mobile_notification_level: UserChatChannelMembership::NOTIFICATION_LEVELS[:always]) + UserChatChannelMembership.where(user: user1, chat_channel: public_chat_channel).update( + mobile_notification_level: UserChatChannelMembership::NOTIFICATION_LEVELS[:always], + ) PresenceChannel.clear_all! end @@ -373,7 +382,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user2, - content: "Beep boop" + content: "Beep boop", ) end @@ -383,7 +392,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user2, - content: "Beep boop" + content: "Beep boop", ) end end @@ -399,7 +408,7 @@ chat_channel: public_chat_channel, user: user1, content: "Beep boop", - upload_ids: [upload1.id] + upload_ids: [upload1.id], ) }.to change { ChatUpload.where(upload_id: upload1.id).count }.by(1) end @@ -410,14 +419,11 @@ chat_channel: public_chat_channel, user: user1, content: "Beep boop", - upload_ids: [upload1.id, upload2.id] + upload_ids: [upload1.id, upload2.id], ) - }.to change { - ChatUpload.where(upload_id: upload1.id).count - }.by(1) - .and change { - ChatUpload.where(upload_id: upload2.id).count - }.by(1) + }.to change { ChatUpload.where(upload_id: upload1.id).count }.by(1).and change { + ChatUpload.where(upload_id: upload2.id).count + }.by(1) end it "filters out uploads that weren't uploaded by the user" do @@ -426,11 +432,9 @@ chat_channel: public_chat_channel, user: user1, content: "Beep boop", - upload_ids: [private_upload.id] + upload_ids: [private_upload.id], ) - }.to change { - ChatUpload.where(upload_id: private_upload.id).count - }.by(0) + }.to change { ChatUpload.where(upload_id: private_upload.id).count }.by(0) end it "doesn't attach uploads when `chat_allow_uploads` is false" do @@ -440,7 +444,7 @@ chat_channel: public_chat_channel, user: user1, content: "Beep boop", - upload_ids: [upload1.id] + upload_ids: [upload1.id], ) }.to change { ChatUpload.where(upload_id: upload1.id).count }.by(0) end @@ -454,7 +458,7 @@ DiscourseChat::ChatMessageCreator.create( chat_channel: public_chat_channel, user: user1, - content: "Hi @#{user2.username}" + content: "Hi @#{user2.username}", ) end.to change { ChatDraft.count }.by(-1) end @@ -463,31 +467,39 @@ fab!(:watched_word) { Fabricate(:watched_word) } it "errors when a blocked word is present" do - creator = DiscourseChat::ChatMessageCreator.create( - chat_channel: public_chat_channel, - user: user1, - content: "bad word - #{watched_word.word}" - ) + creator = + DiscourseChat::ChatMessageCreator.create( + chat_channel: public_chat_channel, + user: user1, + content: "bad word - #{watched_word.word}", + ) expect(creator.failed?).to eq(true) - expect(creator.error.message).to match(I18n.t("contains_blocked_word", { word: watched_word.word })) + expect(creator.error.message).to match( + I18n.t("contains_blocked_word", { word: watched_word.word }), + ) end end describe "channel statuses" do def create_message(user) - DiscourseChat::ChatMessageCreator.create(chat_channel: public_chat_channel, user: user, content: "test message") + DiscourseChat::ChatMessageCreator.create( + chat_channel: public_chat_channel, + user: user, + content: "test message", + ) end context "when channel is closed" do - before do - public_chat_channel.update(status: :closed) - end + before { public_chat_channel.update(status: :closed) } it "errors when trying to create the message for non-staff" do creator = create_message(user1) expect(creator.failed?).to eq(true) expect(creator.error.message).to eq( - I18n.t("chat.errors.channel_new_message_disallowed", status: public_chat_channel.status_name) + I18n.t( + "chat.errors.channel_new_message_disallowed", + status: public_chat_channel.status_name, + ), ) end @@ -497,39 +509,47 @@ def create_message(user) end context "when channel is read_only" do - before do - public_chat_channel.update(status: :read_only) - end + before { public_chat_channel.update(status: :read_only) } it "errors when trying to create the message for all users" do creator = create_message(user1) expect(creator.failed?).to eq(true) expect(creator.error.message).to eq( - I18n.t("chat.errors.channel_new_message_disallowed", status: public_chat_channel.status_name) + I18n.t( + "chat.errors.channel_new_message_disallowed", + status: public_chat_channel.status_name, + ), ) creator = create_message(admin1) expect(creator.failed?).to eq(true) expect(creator.error.message).to eq( - I18n.t("chat.errors.channel_new_message_disallowed", status: public_chat_channel.status_name) + I18n.t( + "chat.errors.channel_new_message_disallowed", + status: public_chat_channel.status_name, + ), ) end end context "when channel is archived" do - before do - public_chat_channel.update(status: :archived) - end + before { public_chat_channel.update(status: :archived) } it "errors when trying to create the message for all users" do creator = create_message(user1) expect(creator.failed?).to eq(true) expect(creator.error.message).to eq( - I18n.t("chat.errors.channel_new_message_disallowed", status: public_chat_channel.status_name) + I18n.t( + "chat.errors.channel_new_message_disallowed", + status: public_chat_channel.status_name, + ), ) creator = create_message(admin1) expect(creator.failed?).to eq(true) expect(creator.error.message).to eq( - I18n.t("chat.errors.channel_new_message_disallowed", status: public_chat_channel.status_name) + I18n.t( + "chat.errors.channel_new_message_disallowed", + status: public_chat_channel.status_name, + ), ) end end diff --git a/spec/components/chat_message_rate_limiter_spec.rb b/spec/components/chat_message_rate_limiter_spec.rb index e6471be3d..e4dc047d6 100644 --- a/spec/components/chat_message_rate_limiter_spec.rb +++ b/spec/components/chat_message_rate_limiter_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" describe DiscourseChat::ChatMessageRateLimiter do fab!(:user) { Fabricate(:user, trust_level: 3) } @@ -14,9 +14,7 @@ SiteSetting.chat_auto_silence_duration = 30 end - after do - limiter.clear! - end + after { limiter.clear! } it "does nothing when rate limits are not exceeded" do limiter.run! @@ -29,9 +27,7 @@ expect(user.reload.silenced?).to be false end - expect { - limiter.run! - }.to raise_error(RateLimiter::LimitExceeded) + expect { limiter.run! }.to raise_error(RateLimiter::LimitExceeded) expect(user.reload.silenced?).to be true expect(user.silenced_till).to be_within(0.1).of(30.minutes.from_now) @@ -41,9 +37,7 @@ user.update(trust_level: 0) # Should only be able to run once without hitting limit limiter.run! expect(user.reload.silenced?).to be false - expect { - limiter.run! - }.to raise_error(RateLimiter::LimitExceeded) + expect { limiter.run! }.to raise_error(RateLimiter::LimitExceeded) expect(user.reload.silenced?).to be true end @@ -61,9 +55,7 @@ limiter.run! expect(user.reload.silenced?).to be false - expect { - limiter.run! - }.to raise_error(RateLimiter::LimitExceeded) + expect { limiter.run! }.to raise_error(RateLimiter::LimitExceeded) expect(user.reload.silenced?).to be false end @@ -71,15 +63,12 @@ SiteSetting.chat_allowed_messages_for_other_trust_levels = 1 limiter.run! - expect { - limiter.run! - }.to raise_error(RateLimiter::LimitExceeded) - .and change { - UserHistory.where( - target_user: user, - acting_user: Discourse.system_user, - action: UserHistory.actions[:silence_user] - ).count - }.by(1) + expect { limiter.run! }.to raise_error(RateLimiter::LimitExceeded).and change { + UserHistory.where( + target_user: user, + acting_user: Discourse.system_user, + action: UserHistory.actions[:silence_user], + ).count + }.by(1) end end diff --git a/spec/components/chat_message_updater_spec.rb b/spec/components/chat_message_updater_spec.rb index 1720e05a5..2b45fab42 100644 --- a/spec/components/chat_message_updater_spec.rb +++ b/spec/components/chat_message_updater_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" describe DiscourseChat::ChatMessageUpdater do fab!(:admin1) { Fabricate(:admin) } @@ -9,7 +9,13 @@ fab!(:user2) { Fabricate(:user) } fab!(:user3) { Fabricate(:user) } fab!(:user4) { Fabricate(:user) } - fab!(:admin_group) { Fabricate(:public_group, users: [admin1, admin2], mentionable_level: Group::ALIAS_LEVELS[:everyone]) } + fab!(:admin_group) do + Fabricate( + :public_group, + users: [admin1, admin2], + mentionable_level: Group::ALIAS_LEVELS[:everyone], + ) + end fab!(:user_without_memberships) { Fabricate(:user) } fab!(:public_chat_channel) { Fabricate(:chat_channel, chatable: Fabricate(:topic)) } @@ -22,17 +28,19 @@ [admin1, admin2, user1, user2, user3, user4].each do |user| Fabricate(:user_chat_channel_membership, chat_channel: public_chat_channel, user: user) end - @direct_message_channel = DiscourseChat::DirectMessageChannelCreator.create!(target_users: [user1, user2]) + @direct_message_channel = + DiscourseChat::DirectMessageChannelCreator.create!(target_users: [user1, user2]) end def create_chat_message(user, message, channel, upload_ids: nil) - creator = DiscourseChat::ChatMessageCreator.create( - chat_channel: channel, - user: user, - in_reply_to_id: nil, - content: message, - upload_ids: upload_ids - ) + creator = + DiscourseChat::ChatMessageCreator.create( + chat_channel: channel, + user: user, + in_reply_to_id: nil, + content: message, + upload_ids: upload_ids, + ) creator.chat_message end @@ -42,12 +50,15 @@ def create_chat_message(user, message, channel, upload_ids: nil) chat_message = create_chat_message(user1, og_message, public_chat_channel) new_message = "2 short" - updater = DiscourseChat::ChatMessageUpdater.update( - chat_message: chat_message, - new_content: new_message - ) + updater = + DiscourseChat::ChatMessageUpdater.update(chat_message: chat_message, new_content: new_message) expect(updater.failed?).to eq(true) - expect(updater.error.message).to match(I18n.t("chat.errors.minimum_length_not_met", { minimum: SiteSetting.chat_minimum_message_length })) + expect(updater.error.message).to match( + I18n.t( + "chat.errors.minimum_length_not_met", + { minimum: SiteSetting.chat_minimum_message_length }, + ), + ) expect(chat_message.reload.message).to eq(og_message) end @@ -55,10 +66,7 @@ def create_chat_message(user, message, channel, upload_ids: nil) chat_message = create_chat_message(user1, "This will be changed", public_chat_channel) new_message = "Change to this!" - DiscourseChat::ChatMessageUpdater.update( - chat_message: chat_message, - new_content: new_message - ) + DiscourseChat::ChatMessageUpdater.update(chat_message: chat_message, new_content: new_message) expect(chat_message.reload.message).to eq(new_message) end @@ -67,12 +75,10 @@ def create_chat_message(user, message, channel, upload_ids: nil) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, - new_content: "this is a message with @system @mentions @#{user2.username} and @#{user3.username}" + new_content: + "this is a message with @system @mentions @#{user2.username} and @#{user3.username}", ) - }.to change { user2.chat_mentions.count }.by(1) - .and change { - user3.chat_mentions.count - }.by(1) + }.to change { user2.chat_mentions.count }.by(1).and change { user3.chat_mentions.count }.by(1) end it "doesn't create mentions for already mentioned users" do @@ -81,7 +87,7 @@ def create_chat_message(user, message, channel, upload_ids: nil) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, - new_content: message + " editedddd" + new_content: message + " editedddd", ) }.to change { ChatMention.count }.by(0) end @@ -93,29 +99,28 @@ def create_chat_message(user, message, channel, upload_ids: nil) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, - new_content: message + " @#{user_without_memberships.username}" + new_content: message + " @#{user_without_memberships.username}", ) }.to change { ChatMention.count }.by(0) end it "destroys mention notifications that should be removed" do - chat_message = create_chat_message(user1, "ping @#{user2.username} @#{user3.username}", public_chat_channel) + chat_message = + create_chat_message(user1, "ping @#{user2.username} @#{user3.username}", public_chat_channel) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, - new_content: "ping @#{user3.username}" + new_content: "ping @#{user3.username}", ) - }.to change { user2.chat_mentions.count }.by(-1) - .and change { - user3.chat_mentions.count - }.by(0) + }.to change { user2.chat_mentions.count }.by(-1).and change { user3.chat_mentions.count }.by(0) end it "creates new, leaves existing, and removes old mentions all at once" do - chat_message = create_chat_message(user1, "ping @#{user2.username} @#{user3.username}", public_chat_channel) + chat_message = + create_chat_message(user1, "ping @#{user2.username} @#{user3.username}", public_chat_channel) DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, - new_content: "ping @#{user3.username} @#{user4.username}" + new_content: "ping @#{user3.username} @#{user4.username}", ) expect(user2.chat_mentions.where(chat_message: chat_message)).not_to be_present @@ -124,11 +129,11 @@ def create_chat_message(user, message, channel, upload_ids: nil) end it "does not create new mentions in direct message for users who don't have access" do - chat_message = create_chat_message(user1, "ping nobody" , @direct_message_channel) + chat_message = create_chat_message(user1, "ping nobody", @direct_message_channel) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, - new_content: "ping @#{admin1.username}" + new_content: "ping @#{admin1.username}", ) }.to change { ChatMention.count }.by(0) end @@ -139,7 +144,7 @@ def create_chat_message(user, message, channel, upload_ids: nil) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, - new_content: "ping @#{admin_group.name}" + new_content: "ping @#{admin_group.name}", ) }.to change { ChatMention.where(chat_message: chat_message).count }.by(2) @@ -152,12 +157,11 @@ def create_chat_message(user, message, channel, upload_ids: nil) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, - new_content: "ping @#{admin_group.name} @#{admin2.username}" + new_content: "ping @#{admin_group.name} @#{admin2.username}", ) - }.to change { admin1.chat_mentions.count }.by(1) - .and change { - admin2.chat_mentions.count - }.by(0) + }.to change { admin1.chat_mentions.count }.by(1).and change { admin2.chat_mentions.count }.by( + 0, + ) end it "deletes old mentions when group mention is removed" do @@ -165,7 +169,7 @@ def create_chat_message(user, message, channel, upload_ids: nil) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, - new_content: "ping nobody anymore!" + new_content: "ping nobody anymore!", ) }.to change { ChatMention.where(chat_message: chat_message).count }.by(-2) @@ -178,10 +182,7 @@ def create_chat_message(user, message, channel, upload_ids: nil) old_message = "It's a thrsday!" new_message = "It's a thursday!" chat_message = create_chat_message(user1, old_message, public_chat_channel) - DiscourseChat::ChatMessageUpdater.update( - chat_message: chat_message, - new_content: new_message - ) + DiscourseChat::ChatMessageUpdater.update(chat_message: chat_message, new_content: new_message) revision = chat_message.revisions.last expect(revision.old_message).to eq(old_message) expect(revision.new_message).to eq(new_message) @@ -192,34 +193,52 @@ def create_chat_message(user, message, channel, upload_ids: nil) fab!(:upload2) { Fabricate(:upload, user: user1) } it "does nothing if the passed in upload_ids match the existing upload_ids" do - chat_message = create_chat_message(user1, "something", public_chat_channel, upload_ids: [upload1.id, upload2.id]) + chat_message = + create_chat_message( + user1, + "something", + public_chat_channel, + upload_ids: [upload1.id, upload2.id], + ) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, new_content: "I guess this is different", - upload_ids: [upload2.id, upload1.id] + upload_ids: [upload2.id, upload1.id], ) }.to change { ChatUpload.count }.by(0) end it "removes uploads that should be removed" do - chat_message = create_chat_message(user1, "something", public_chat_channel, upload_ids: [upload1.id, upload2.id]) + chat_message = + create_chat_message( + user1, + "something", + public_chat_channel, + upload_ids: [upload1.id, upload2.id], + ) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, new_content: "I guess this is different", - upload_ids: [upload1.id] + upload_ids: [upload1.id], ) }.to change { ChatUpload.where(upload_id: upload2.id).count }.by(-1) end it "removes all uploads if they should be removed" do - chat_message = create_chat_message(user1, "something", public_chat_channel, upload_ids: [upload1.id, upload2.id]) + chat_message = + create_chat_message( + user1, + "something", + public_chat_channel, + upload_ids: [upload1.id, upload2.id], + ) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, new_content: "I guess this is different", - upload_ids: [] + upload_ids: [], ) }.to change { ChatUpload.where(chat_message: chat_message).count }.by(-2) end @@ -230,7 +249,7 @@ def create_chat_message(user, message, channel, upload_ids: nil) DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, new_content: "I guess this is different", - upload_ids: [upload1.id] + upload_ids: [upload1.id], ) }.to change { ChatUpload.where(chat_message: chat_message).count }.by(1) end @@ -241,18 +260,19 @@ def create_chat_message(user, message, channel, upload_ids: nil) DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, new_content: "I guess this is different", - upload_ids: [upload1.id, upload2.id] + upload_ids: [upload1.id, upload2.id], ) }.to change { ChatUpload.where(chat_message: chat_message).count }.by(2) end it "doesn't remove existing uploads when BS upload ids are passed in" do - chat_message = create_chat_message(user1, "something", public_chat_channel, upload_ids: [upload1.id]) + chat_message = + create_chat_message(user1, "something", public_chat_channel, upload_ids: [upload1.id]) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, new_content: "I guess this is different", - upload_ids: [0] + upload_ids: [0], ) }.to change { ChatUpload.where(chat_message: chat_message).count }.by(0) end @@ -264,31 +284,43 @@ def create_chat_message(user, message, channel, upload_ids: nil) DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, new_content: "I guess this is different", - upload_ids: [upload1.id, upload2.id] + upload_ids: [upload1.id, upload2.id], ) }.to change { ChatUpload.where(chat_message: chat_message).count }.by(0) end it "doesn't remove existing uploads if `chat_allow_uploads` is false" do SiteSetting.chat_allow_uploads = false - chat_message = create_chat_message(user1, "something", public_chat_channel, upload_ids: [upload1.id, upload2.id]) + chat_message = + create_chat_message( + user1, + "something", + public_chat_channel, + upload_ids: [upload1.id, upload2.id], + ) expect { DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, new_content: "I guess this is different", - upload_ids: [] + upload_ids: [], ) }.to change { ChatUpload.where(chat_message: chat_message).count }.by(0) end it "updates if upload is present even if length is less than `chat_minimum_message_length`" do - chat_message = create_chat_message(user1, "something", public_chat_channel, upload_ids: [upload1.id, upload2.id]) + chat_message = + create_chat_message( + user1, + "something", + public_chat_channel, + upload_ids: [upload1.id, upload2.id], + ) SiteSetting.chat_minimum_message_length = 10 new_message = "hi :)" DiscourseChat::ChatMessageUpdater.update( chat_message: chat_message, new_content: new_message, - upload_ids: [upload1.id] + upload_ids: [upload1.id], ) expect(chat_message.reload.message).to eq(new_message) end @@ -299,13 +331,16 @@ def create_chat_message(user, message, channel, upload_ids: nil) it "errors when a blocked word is present" do chat_message = create_chat_message(user1, "something", public_chat_channel) - creator = DiscourseChat::ChatMessageCreator.create( - chat_channel: public_chat_channel, - user: user1, - content: "bad word - #{watched_word.word}" - ) + creator = + DiscourseChat::ChatMessageCreator.create( + chat_channel: public_chat_channel, + user: user1, + content: "bad word - #{watched_word.word}", + ) expect(creator.failed?).to eq(true) - expect(creator.error.message).to match(I18n.t("contains_blocked_word", { word: watched_word.word })) + expect(creator.error.message).to match( + I18n.t("contains_blocked_word", { word: watched_word.word }), + ) end end @@ -316,20 +351,21 @@ def update_message(user) message.update(user: user) DiscourseChat::ChatMessageUpdater.update( chat_message: message, - new_content: "I guess this is different" + new_content: "I guess this is different", ) end context "when channel is closed" do - before do - public_chat_channel.update(status: :closed) - end + before { public_chat_channel.update(status: :closed) } it "errors when trying to update the message for non-staff" do updater = update_message(user1) expect(updater.failed?).to eq(true) expect(updater.error.message).to eq( - I18n.t("chat.errors.channel_modify_message_disallowed", status: public_chat_channel.status_name) + I18n.t( + "chat.errors.channel_modify_message_disallowed", + status: public_chat_channel.status_name, + ), ) end @@ -340,39 +376,47 @@ def update_message(user) end context "when channel is read_only" do - before do - public_chat_channel.update(status: :read_only) - end + before { public_chat_channel.update(status: :read_only) } it "errors when trying to update the message for all users" do updater = update_message(user1) expect(updater.failed?).to eq(true) expect(updater.error.message).to eq( - I18n.t("chat.errors.channel_modify_message_disallowed", status: public_chat_channel.status_name) + I18n.t( + "chat.errors.channel_modify_message_disallowed", + status: public_chat_channel.status_name, + ), ) updater = update_message(admin1) expect(updater.failed?).to eq(true) expect(updater.error.message).to eq( - I18n.t("chat.errors.channel_modify_message_disallowed", status: public_chat_channel.status_name) + I18n.t( + "chat.errors.channel_modify_message_disallowed", + status: public_chat_channel.status_name, + ), ) end end context "when channel is archived" do - before do - public_chat_channel.update(status: :archived) - end + before { public_chat_channel.update(status: :archived) } it "errors when trying to update the message for all users" do updater = update_message(user1) expect(updater.failed?).to eq(true) expect(updater.error.message).to eq( - I18n.t("chat.errors.channel_modify_message_disallowed", status: public_chat_channel.status_name) + I18n.t( + "chat.errors.channel_modify_message_disallowed", + status: public_chat_channel.status_name, + ), ) updater = update_message(admin1) expect(updater.failed?).to eq(true) expect(updater.error.message).to eq( - I18n.t("chat.errors.channel_modify_message_disallowed", status: public_chat_channel.status_name) + I18n.t( + "chat.errors.channel_modify_message_disallowed", + status: public_chat_channel.status_name, + ), ) end end diff --git a/spec/components/chat_seeder_spec.rb b/spec/components/chat_seeder_spec.rb index fd1c8ddb6..957b2c6b8 100644 --- a/spec/components/chat_seeder_spec.rb +++ b/spec/components/chat_seeder_spec.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" describe ChatSeeder do fab!(:staff_category) { Fabricate(:private_category, group: Group[:staff]) } @@ -22,7 +22,8 @@ def assert_channel_was_correctly_seeded(channel, group) expect(channel.auto_join_users).to eq(true) expected_members_count = GroupUser.where(group: group).count - memberships_count = UserChatChannelMembership.automatic.where(chat_channel: channel, following: true).count + memberships_count = + UserChatChannelMembership.automatic.where(chat_channel: channel, following: true).count expect(memberships_count).to eq(expected_members_count) end @@ -41,8 +42,8 @@ def assert_channel_was_correctly_seeded(channel, group) expect(SiteSetting.needs_chat_seeded).to eq(false) end - it 'applies a different name to the meta category channel' do - expected_name = I18n.t('chat.channel.default_titles.site_feedback') + it "applies a different name to the meta category channel" do + expected_name = I18n.t("chat.channel.default_titles.site_feedback") ChatSeeder.new.execute diff --git a/spec/fabricators/chat_fabricator.rb b/spec/fabricators/chat_fabricator.rb index 36131488d..21c909110 100644 --- a/spec/fabricators/chat_fabricator.rb +++ b/spec/fabricators/chat_fabricator.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true Fabricator(:chat_channel) do - name { ["Gaming Lounge", "Music Lodge", "Random", "Politics", "Sports Center", "Kino Buffs"].sample } + name do + ["Gaming Lounge", "Music Lodge", "Random", "Politics", "Sports Center", "Kino Buffs"].sample + end chatable { Fabricate(:category) } status { :open } end @@ -23,7 +25,7 @@ Fabricator(:chat_message_reaction) do chat_message { Fabricate(:chat_message) } user { Fabricate(:user) } - emoji { ["+1", "tada", "heart", "joffrey_facepalm"].sample } + emoji { %w[+1 tada heart joffrey_facepalm].sample } end Fabricator(:chat_upload) do @@ -39,22 +41,20 @@ Fabricator(:reviewable_chat_message) do reviewable_by_moderator true - type 'ReviewableChatMessage' + type "ReviewableChatMessage" created_by { Fabricate(:user) } - target_type 'ChatMessage' + target_type "ChatMessage" target { Fabricate(:chat_message) } - reviewable_scores { |p| [ - Fabricate.build(:reviewable_score, reviewable_id: p[:id]), - ]} + reviewable_scores { |p| [Fabricate.build(:reviewable_score, reviewable_id: p[:id])] } end -Fabricator(:direct_message_channel) do - users { [Fabricate(:user), Fabricate(:user)] } -end +Fabricator(:direct_message_channel) { users { [Fabricate(:user), Fabricate(:user)] } } Fabricator(:chat_webhook_event) do chat_message { Fabricate(:chat_message) } - incoming_chat_webhook { |attrs| Fabricate(:incoming_chat_webhook, chat_channel: attrs[:chat_message].chat_channel) } + incoming_chat_webhook do |attrs| + Fabricate(:incoming_chat_webhook, chat_channel: attrs[:chat_message].chat_channel) + end end Fabricator(:incoming_chat_webhook) do diff --git a/spec/integration/custom_api_key_scopes_spec.rb b/spec/integration/custom_api_key_scopes_spec.rb index 2d6fbd2aa..37ea37f01 100644 --- a/spec/integration/custom_api_key_scopes_spec.rb +++ b/spec/integration/custom_api_key_scopes_spec.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" -describe 'API keys scoped to chat#create_message' do +describe "API keys scoped to chat#create_message" do before do SiteSetting.chat_enabled = true SiteSetting.chat_allowed_groups = Group::AUTO_GROUPS[:everyone] @@ -14,11 +14,7 @@ let(:chat_api_key) do key = ApiKey.create! - ApiKeyScope.create!( - resource: "chat", - action: "create_message", - api_key_id: key.id - ) + ApiKeyScope.create!(resource: "chat", action: "create_message", api_key_id: key.id) key end @@ -28,32 +24,36 @@ resource: "chat", action: "create_message", api_key_id: key.id, - allowed_parameters: { "chat_channel_id" => [chat_channel_2.id.to_s] } + allowed_parameters: { + "chat_channel_id" => [chat_channel_2.id.to_s], + }, ) key end - it 'cannot hit any other endpoints' do - get "/admin/users/list/active.json", headers: { - "Api-Key" => chat_api_key.key, - "Api-Username" => admin.username - } + it "cannot hit any other endpoints" do + get "/admin/users/list/active.json", + headers: { + "Api-Key" => chat_api_key.key, + "Api-Username" => admin.username, + } expect(response.status).to eq(404) - get "/latest.json", headers: { - "Api-Key" => chat_api_key.key, - "Api-Username" => admin.username - } + get "/latest.json", headers: { "Api-Key" => chat_api_key.key, "Api-Username" => admin.username } expect(response.status).to eq(403) end it "can create chat messages" do UserChatChannelMembership.create(user: admin, chat_channel: chat_channel, following: true) expect { - post "/chat/#{chat_channel.id}.json", headers: { - "Api-Key" => chat_api_key.key, - "Api-Username" => admin.username - }, params: { message: "asdfasdf asdfasdf" } + post "/chat/#{chat_channel.id}.json", + headers: { + "Api-Key" => chat_api_key.key, + "Api-Username" => admin.username, + }, + params: { + message: "asdfasdf asdfasdf", + } }.to change { ChatMessage.where(chat_channel: chat_channel).count }.by(1) expect(response.status).to eq(200) end @@ -61,10 +61,14 @@ it "cannot post in a channel it is not scoped for" do UserChatChannelMembership.create(user: admin, chat_channel: chat_channel, following: true) expect { - post "/chat/#{chat_channel.id}.json", headers: { - "Api-Key" => chat_channel_2_api_key.key, - "Api-Username" => admin.username - }, params: { message: "asdfasdf asdfasdf" } + post "/chat/#{chat_channel.id}.json", + headers: { + "Api-Key" => chat_channel_2_api_key.key, + "Api-Username" => admin.username, + }, + params: { + message: "asdfasdf asdfasdf", + } }.to change { ChatMessage.where(chat_channel: chat_channel).count }.by(0) expect(response.status).to eq(403) end @@ -72,10 +76,14 @@ it "can only post in scoped channels" do UserChatChannelMembership.create(user: admin, chat_channel: chat_channel_2, following: true) expect { - post "/chat/#{chat_channel_2.id}.json", headers: { - "Api-Key" => chat_channel_2_api_key.key, - "Api-Username" => admin.username - }, params: { message: "asdfasdf asdfasdf" } + post "/chat/#{chat_channel_2.id}.json", + headers: { + "Api-Key" => chat_channel_2_api_key.key, + "Api-Username" => admin.username, + }, + params: { + message: "asdfasdf asdfasdf", + } }.to change { ChatMessage.where(chat_channel: chat_channel_2).count }.by(1) expect(response.status).to eq(200) end diff --git a/spec/integration/plugin_api_spec.rb b/spec/integration/plugin_api_spec.rb index 4580e8b82..a26b24b54 100644 --- a/spec/integration/plugin_api_spec.rb +++ b/spec/integration/plugin_api_spec.rb @@ -1,15 +1,13 @@ # frozen_string_literal: true -require 'rails_helper' +require "rails_helper" -describe 'Plugin API for discourse_chat' do - before do - SiteSetting.chat_enabled = true - end +describe "Plugin API for discourse_chat" do + before { SiteSetting.chat_enabled = true } let(:metadata) do metadata = Plugin::Metadata.new - metadata.name = 'test' + metadata.name = "test" metadata end @@ -19,8 +17,8 @@ plugin end - context 'discourse_chat.enable_markdown_feature' do - it 'stores the markdown feature' do + context "discourse_chat.enable_markdown_feature" do + it "stores the markdown feature" do plugin_instance.discourse_chat.enable_markdown_feature(:foo) expect(DiscoursePluginRegistry.chat_markdown_features.include?(:foo)).to be_truthy diff --git a/spec/integration/post_chat_quote_spec.rb b/spec/integration/post_chat_quote_spec.rb index 4f9d552cb..b68c070e3 100644 --- a/spec/integration/post_chat_quote_spec.rb +++ b/spec/integration/post_chat_quote_spec.rb @@ -3,12 +3,12 @@ describe "chat bbcode quoting in posts" do fab!(:post) { Fabricate(:post) } - before do - SiteSetting.chat_enabled = true - end + before { SiteSetting.chat_enabled = true } it "can render the simplest version" do - post.update!(raw: "[chat quote=\"martin;2321;2022-01-25T05:40:39Z\"]\nThis is a chat message.\n[/chat]") + post.update!( + raw: "[chat quote=\"martin;2321;2022-01-25T05:40:39Z\"]\nThis is a chat message.\n[/chat]", + ) expect(post.cooked.chomp).to eq(<<~COOKED.chomp)