From 766bcbc6840c9d665055441bcd77616b3a96e10e Mon Sep 17 00:00:00 2001 From: Martin Brennan <martin@discourse.org> Date: Mon, 7 Nov 2022 09:04:47 +1000 Subject: [PATCH] FIX: Add editing user ids to ChatMessage and ChatMessageRevision (#18877) This commit adds last_editor_id to ChatMessage for parity with Post in core, as well as adding user_id to the ChatMessageRevision record since we need to know who is making edits and revisions to messages, in case in future we want to allow more than just the current user to edit chat messages. The backfill for data here simply uses the record's creating user ID, but in future if we allow other people to edit the messages it will use their ID. --- .../chat/app/controllers/chat_controller.rb | 1 + plugins/chat/app/models/chat_message.rb | 5 ++ .../chat/app/models/chat_message_revision.rb | 3 ++ ...319_add_last_editor_id_to_chat_messages.rb | 11 ++++ ...ser_ids_for_chat_messages_and_revisions.rb | 17 +++++++ plugins/chat/lib/chat_message_updater.rb | 11 +++- .../components/chat_message_creator_spec.rb | 10 ++++ .../components/chat_message_updater_spec.rb | 50 ++++++++++++++++--- .../chat/spec/lib/chat_review_queue_spec.rb | 1 + 9 files changed, 102 insertions(+), 7 deletions(-) create mode 100644 plugins/chat/db/migrate/20221101061319_add_last_editor_id_to_chat_messages.rb create mode 100644 plugins/chat/db/post_migrate/20221101071135_backfill_editing_user_ids_for_chat_messages_and_revisions.rb diff --git a/plugins/chat/app/controllers/chat_controller.rb b/plugins/chat/app/controllers/chat_controller.rb index 287bcd8f359..f84ce9b2739 100644 --- a/plugins/chat/app/controllers/chat_controller.rb +++ b/plugins/chat/app/controllers/chat_controller.rb @@ -147,6 +147,7 @@ class Chat::ChatController < Chat::ChatBaseController guardian.ensure_can_edit_chat!(@message) chat_message_updater = Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: @message, new_content: params[:new_message], upload_ids: params[:upload_ids] || [], diff --git a/plugins/chat/app/models/chat_message.rb b/plugins/chat/app/models/chat_message.rb index b0784d4ccb6..f54c4627381 100644 --- a/plugins/chat/app/models/chat_message.rb +++ b/plugins/chat/app/models/chat_message.rb @@ -9,6 +9,7 @@ class ChatMessage < ActiveRecord::Base belongs_to :chat_channel belongs_to :user belongs_to :in_reply_to, class_name: "ChatMessage" + belongs_to :last_editor, class_name: "User" has_many :replies, class_name: "ChatMessage", foreign_key: "in_reply_to_id", dependent: :nullify has_many :revisions, class_name: "ChatMessageRevision", dependent: :destroy has_many :reactions, class_name: "ChatMessageReaction", dependent: :destroy @@ -32,6 +33,8 @@ class ChatMessage < ActiveRecord::Base scope :created_before, ->(date) { where("chat_messages.created_at < ?", date) } + before_save { self.last_editor_id ||= self.user_id } + def validate_message(has_uploads:) WatchedWordsValidator.new(attributes: [:message]).validate(self) Chat::DuplicateMessageValidator.new(self).validate @@ -207,9 +210,11 @@ end # message :text # cooked :text # cooked_version :integer +# last_editor_id :integer # # Indexes # # idx_chat_messages_by_created_at_not_deleted (created_at) WHERE (deleted_at IS NULL) # index_chat_messages_on_chat_channel_id_and_created_at (chat_channel_id,created_at) +# index_chat_messages_on_last_editor_id (last_editor_id) # diff --git a/plugins/chat/app/models/chat_message_revision.rb b/plugins/chat/app/models/chat_message_revision.rb index 03f19168350..a0caed17fbd 100644 --- a/plugins/chat/app/models/chat_message_revision.rb +++ b/plugins/chat/app/models/chat_message_revision.rb @@ -2,6 +2,7 @@ class ChatMessageRevision < ActiveRecord::Base belongs_to :chat_message + belongs_to :user end # == Schema Information @@ -14,8 +15,10 @@ end # new_message :text not null # created_at :datetime not null # updated_at :datetime not null +# user_id :integer # # Indexes # # index_chat_message_revisions_on_chat_message_id (chat_message_id) +# index_chat_message_revisions_on_user_id (user_id) # diff --git a/plugins/chat/db/migrate/20221101061319_add_last_editor_id_to_chat_messages.rb b/plugins/chat/db/migrate/20221101061319_add_last_editor_id_to_chat_messages.rb new file mode 100644 index 00000000000..feb782b127e --- /dev/null +++ b/plugins/chat/db/migrate/20221101061319_add_last_editor_id_to_chat_messages.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddLastEditorIdToChatMessages < ActiveRecord::Migration[7.0] + def change + add_column :chat_messages, :last_editor_id, :integer + add_column :chat_message_revisions, :user_id, :integer + + add_index :chat_messages, :last_editor_id + add_index :chat_message_revisions, :user_id + end +end diff --git a/plugins/chat/db/post_migrate/20221101071135_backfill_editing_user_ids_for_chat_messages_and_revisions.rb b/plugins/chat/db/post_migrate/20221101071135_backfill_editing_user_ids_for_chat_messages_and_revisions.rb new file mode 100644 index 00000000000..01a736844e9 --- /dev/null +++ b/plugins/chat/db/post_migrate/20221101071135_backfill_editing_user_ids_for_chat_messages_and_revisions.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class BackfillEditingUserIdsForChatMessagesAndRevisions < ActiveRecord::Migration[7.0] + def up + DB.exec("UPDATE chat_messages SET last_editor_id = user_id") + DB.exec(<<~SQL) + UPDATE chat_message_revisions cmr + SET user_id = cm.user_id + FROM chat_messages AS cm + WHERE cmr.chat_message_id = cm.id + SQL + end + + def down + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/plugins/chat/lib/chat_message_updater.rb b/plugins/chat/lib/chat_message_updater.rb index df67587343c..39636cbcd07 100644 --- a/plugins/chat/lib/chat_message_updater.rb +++ b/plugins/chat/lib/chat_message_updater.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + class Chat::ChatMessageUpdater attr_reader :error @@ -8,7 +9,9 @@ class Chat::ChatMessageUpdater instance end - def initialize(chat_message:, new_content:, upload_ids: nil) + def initialize(guardian:, chat_message:, new_content:, upload_ids: nil) + @guardian = guardian + @user = guardian.user @chat_message = chat_message @old_message_content = chat_message.message @chat_channel = @chat_message.chat_channel @@ -23,6 +26,7 @@ class Chat::ChatMessageUpdater begin validate_channel_status! @chat_message.message = @new_content + @chat_message.last_editor_id = @user.id upload_info = get_upload_info validate_message!(has_uploads: upload_info[:uploads].any?) @chat_message.cook @@ -43,6 +47,10 @@ class Chat::ChatMessageUpdater private + # TODO (martin) Since we have guardian here now we should move + # guardian.ensure_can_edit_chat!(@message) from the controller into + # this class. + def validate_channel_status! return if @guardian.can_modify_channel_message?(@chat_channel) raise StandardError.new( @@ -86,6 +94,7 @@ class Chat::ChatMessageUpdater @chat_message.revisions.create!( old_message: @old_message_content, new_message: @chat_message.message, + user_id: @user.id, ) end end diff --git a/plugins/chat/spec/components/chat_message_creator_spec.rb b/plugins/chat/spec/components/chat_message_creator_spec.rb index 1128ab9cc9d..1ead27d5b20 100644 --- a/plugins/chat/spec/components/chat_message_creator_spec.rb +++ b/plugins/chat/spec/components/chat_message_creator_spec.rb @@ -95,6 +95,16 @@ describe Chat::ChatMessageCreator do }.to change { ChatMessage.count }.by(1) end + it "sets the last_editor_id to the user who created the message" do + message = + Chat::ChatMessageCreator.create( + chat_channel: public_chat_channel, + user: user1, + content: "this is a message", + ).chat_message + expect(message.last_editor_id).to eq(user1.id) + end + it "creates mention notifications for public chat" do expect { Chat::ChatMessageCreator.create( diff --git a/plugins/chat/spec/components/chat_message_updater_spec.rb b/plugins/chat/spec/components/chat_message_updater_spec.rb index 048de7aae9e..37aea837328 100644 --- a/plugins/chat/spec/components/chat_message_updater_spec.rb +++ b/plugins/chat/spec/components/chat_message_updater_spec.rb @@ -3,6 +3,7 @@ require "rails_helper" describe Chat::ChatMessageUpdater do + let(:guardian) { Guardian.new(user1) } fab!(:admin1) { Fabricate(:admin) } fab!(:admin2) { Fabricate(:admin) } fab!(:user1) { Fabricate(:user) } @@ -30,7 +31,10 @@ describe Chat::ChatMessageUpdater do end Group.refresh_automatic_groups! @direct_message_channel = - Chat::DirectMessageChannelCreator.create!(acting_user: user1, target_users: [user1, user2]) + Chat::DirectMessageChannelCreator.create!( + acting_user: user1, + target_users: [user1, user2], + ) end def create_chat_message(user, message, channel, upload_ids: nil) @@ -51,7 +55,12 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, og_message, public_chat_channel) new_message = "2 short" - updater = Chat::ChatMessageUpdater.update(chat_message: chat_message, new_content: new_message) + updater = + Chat::ChatMessageUpdater.update( + guardian: guardian, + chat_message: chat_message, + new_content: new_message, + ) expect(updater.failed?).to eq(true) expect(updater.error.message).to match( I18n.t( @@ -66,7 +75,11 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, "This will be changed", public_chat_channel) new_message = "Change to this!" - Chat::ChatMessageUpdater.update(chat_message: chat_message, new_content: new_message) + Chat::ChatMessageUpdater.update( + guardian: guardian, + chat_message: chat_message, + new_content: new_message, + ) expect(chat_message.reload.message).to eq(new_message) end @@ -74,6 +87,7 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, "This will be changed", public_chat_channel) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "this is a message with @system @mentions @#{user2.username} and @#{user3.username}", @@ -86,6 +100,7 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, message, public_chat_channel) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: message + " editedddd", ) @@ -98,6 +113,7 @@ describe Chat::ChatMessageUpdater do expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: message + " @#{user_without_memberships.username}", ) @@ -109,6 +125,7 @@ describe Chat::ChatMessageUpdater do create_chat_message(user1, "ping @#{user2.username} @#{user3.username}", public_chat_channel) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "ping @#{user3.username}", ) @@ -119,6 +136,7 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, "ping @#{user2.username} @#{user3.username}", public_chat_channel) Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "ping @#{user3.username} @#{user4.username}", ) @@ -132,6 +150,7 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, "ping nobody", @direct_message_channel) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "ping @#{admin1.username}", ) @@ -143,6 +162,7 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, "ping nobody", public_chat_channel) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "ping @#{admin_group.name}", ) @@ -156,6 +176,7 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, "ping @#{admin2.username}", public_chat_channel) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "ping @#{admin_group.name} @#{admin2.username}", ) @@ -166,6 +187,7 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, "ping @#{admin_group.name}", public_chat_channel) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "ping nobody anymore!", ) @@ -176,14 +198,20 @@ describe Chat::ChatMessageUpdater do end end - it "creates a chat_message_revision record" do + it "creates a chat_message_revision record and sets last_editor_id for the message" do old_message = "It's a thrsday!" new_message = "It's a thursday!" chat_message = create_chat_message(user1, old_message, public_chat_channel) - Chat::ChatMessageUpdater.update(chat_message: chat_message, new_content: new_message) + Chat::ChatMessageUpdater.update( + guardian: guardian, + 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) + expect(revision.user_id).to eq(guardian.user.id) + expect(chat_message.reload.last_editor_id).to eq(guardian.user.id) end describe "uploads" do @@ -200,6 +228,7 @@ describe Chat::ChatMessageUpdater do ) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "I guess this is different", upload_ids: [upload2.id, upload1.id], @@ -217,6 +246,7 @@ describe Chat::ChatMessageUpdater do ) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "I guess this is different", upload_ids: [upload1.id], @@ -234,6 +264,7 @@ describe Chat::ChatMessageUpdater do ) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "I guess this is different", upload_ids: [], @@ -245,6 +276,7 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, "something", public_chat_channel) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "I guess this is different", upload_ids: [upload1.id], @@ -256,6 +288,7 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, "something", public_chat_channel) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "I guess this is different", upload_ids: [upload1.id, upload2.id], @@ -263,11 +296,12 @@ describe Chat::ChatMessageUpdater do }.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 + it "doesn't remove existing uploads when upload ids that do not exist are passed in" do chat_message = create_chat_message(user1, "something", public_chat_channel, upload_ids: [upload1.id]) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "I guess this is different", upload_ids: [0], @@ -280,6 +314,7 @@ describe Chat::ChatMessageUpdater do chat_message = create_chat_message(user1, "something", public_chat_channel) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "I guess this is different", upload_ids: [upload1.id, upload2.id], @@ -298,6 +333,7 @@ describe Chat::ChatMessageUpdater do ) expect { Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: "I guess this is different", upload_ids: [], @@ -316,6 +352,7 @@ describe Chat::ChatMessageUpdater do SiteSetting.chat_minimum_message_length = 10 new_message = "hi :)" Chat::ChatMessageUpdater.update( + guardian: guardian, chat_message: chat_message, new_content: new_message, upload_ids: [upload1.id], @@ -348,6 +385,7 @@ describe Chat::ChatMessageUpdater do def update_message(user) message.update(user: user) Chat::ChatMessageUpdater.update( + guardian: Guardian.new(user), chat_message: message, new_content: "I guess this is different", ) diff --git a/plugins/chat/spec/lib/chat_review_queue_spec.rb b/plugins/chat/spec/lib/chat_review_queue_spec.rb index 2a1023fa158..23433dab010 100644 --- a/plugins/chat/spec/lib/chat_review_queue_spec.rb +++ b/plugins/chat/spec/lib/chat_review_queue_spec.rb @@ -117,6 +117,7 @@ describe Chat::ChatReviewQueue do it "ignores the cooldown window when the message is edited" do Chat::ChatMessageUpdater.update( + guardian: Guardian.new(message.user), chat_message: message, new_content: "I'm editing this message. Please flag it.", )