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.
This commit is contained in:
Martin Brennan 2022-11-07 09:04:47 +10:00 committed by GitHub
parent 5a7b478fee
commit 766bcbc684
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 102 additions and 7 deletions

View File

@ -147,6 +147,7 @@ class Chat::ChatController < Chat::ChatBaseController
guardian.ensure_can_edit_chat!(@message) guardian.ensure_can_edit_chat!(@message)
chat_message_updater = chat_message_updater =
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: @message, chat_message: @message,
new_content: params[:new_message], new_content: params[:new_message],
upload_ids: params[:upload_ids] || [], upload_ids: params[:upload_ids] || [],

View File

@ -9,6 +9,7 @@ class ChatMessage < ActiveRecord::Base
belongs_to :chat_channel belongs_to :chat_channel
belongs_to :user belongs_to :user
belongs_to :in_reply_to, class_name: "ChatMessage" 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 :replies, class_name: "ChatMessage", foreign_key: "in_reply_to_id", dependent: :nullify
has_many :revisions, class_name: "ChatMessageRevision", dependent: :destroy has_many :revisions, class_name: "ChatMessageRevision", dependent: :destroy
has_many :reactions, class_name: "ChatMessageReaction", 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) } scope :created_before, ->(date) { where("chat_messages.created_at < ?", date) }
before_save { self.last_editor_id ||= self.user_id }
def validate_message(has_uploads:) def validate_message(has_uploads:)
WatchedWordsValidator.new(attributes: [:message]).validate(self) WatchedWordsValidator.new(attributes: [:message]).validate(self)
Chat::DuplicateMessageValidator.new(self).validate Chat::DuplicateMessageValidator.new(self).validate
@ -207,9 +210,11 @@ end
# message :text # message :text
# cooked :text # cooked :text
# cooked_version :integer # cooked_version :integer
# last_editor_id :integer
# #
# Indexes # Indexes
# #
# idx_chat_messages_by_created_at_not_deleted (created_at) WHERE (deleted_at IS NULL) # 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_chat_channel_id_and_created_at (chat_channel_id,created_at)
# index_chat_messages_on_last_editor_id (last_editor_id)
# #

View File

@ -2,6 +2,7 @@
class ChatMessageRevision < ActiveRecord::Base class ChatMessageRevision < ActiveRecord::Base
belongs_to :chat_message belongs_to :chat_message
belongs_to :user
end end
# == Schema Information # == Schema Information
@ -14,8 +15,10 @@ end
# new_message :text not null # new_message :text not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# user_id :integer
# #
# Indexes # Indexes
# #
# index_chat_message_revisions_on_chat_message_id (chat_message_id) # index_chat_message_revisions_on_chat_message_id (chat_message_id)
# index_chat_message_revisions_on_user_id (user_id)
# #

View File

@ -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

View File

@ -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

View File

@ -1,4 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
class Chat::ChatMessageUpdater class Chat::ChatMessageUpdater
attr_reader :error attr_reader :error
@ -8,7 +9,9 @@ class Chat::ChatMessageUpdater
instance instance
end 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 @chat_message = chat_message
@old_message_content = chat_message.message @old_message_content = chat_message.message
@chat_channel = @chat_message.chat_channel @chat_channel = @chat_message.chat_channel
@ -23,6 +26,7 @@ class Chat::ChatMessageUpdater
begin begin
validate_channel_status! validate_channel_status!
@chat_message.message = @new_content @chat_message.message = @new_content
@chat_message.last_editor_id = @user.id
upload_info = get_upload_info upload_info = get_upload_info
validate_message!(has_uploads: upload_info[:uploads].any?) validate_message!(has_uploads: upload_info[:uploads].any?)
@chat_message.cook @chat_message.cook
@ -43,6 +47,10 @@ class Chat::ChatMessageUpdater
private 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! def validate_channel_status!
return if @guardian.can_modify_channel_message?(@chat_channel) return if @guardian.can_modify_channel_message?(@chat_channel)
raise StandardError.new( raise StandardError.new(
@ -86,6 +94,7 @@ class Chat::ChatMessageUpdater
@chat_message.revisions.create!( @chat_message.revisions.create!(
old_message: @old_message_content, old_message: @old_message_content,
new_message: @chat_message.message, new_message: @chat_message.message,
user_id: @user.id,
) )
end end
end end

View File

@ -95,6 +95,16 @@ describe Chat::ChatMessageCreator do
}.to change { ChatMessage.count }.by(1) }.to change { ChatMessage.count }.by(1)
end 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 it "creates mention notifications for public chat" do
expect { expect {
Chat::ChatMessageCreator.create( Chat::ChatMessageCreator.create(

View File

@ -3,6 +3,7 @@
require "rails_helper" require "rails_helper"
describe Chat::ChatMessageUpdater do describe Chat::ChatMessageUpdater do
let(:guardian) { Guardian.new(user1) }
fab!(:admin1) { Fabricate(:admin) } fab!(:admin1) { Fabricate(:admin) }
fab!(:admin2) { Fabricate(:admin) } fab!(:admin2) { Fabricate(:admin) }
fab!(:user1) { Fabricate(:user) } fab!(:user1) { Fabricate(:user) }
@ -30,7 +31,10 @@ describe Chat::ChatMessageUpdater do
end end
Group.refresh_automatic_groups! Group.refresh_automatic_groups!
@direct_message_channel = @direct_message_channel =
Chat::DirectMessageChannelCreator.create!(acting_user: user1, target_users: [user1, user2]) Chat::DirectMessageChannelCreator.create!(
acting_user: user1,
target_users: [user1, user2],
)
end end
def create_chat_message(user, message, channel, upload_ids: nil) 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) chat_message = create_chat_message(user1, og_message, public_chat_channel)
new_message = "2 short" 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.failed?).to eq(true)
expect(updater.error.message).to match( expect(updater.error.message).to match(
I18n.t( I18n.t(
@ -66,7 +75,11 @@ describe Chat::ChatMessageUpdater do
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel) chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
new_message = "Change to this!" new_message = "Change to this!"
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) expect(chat_message.reload.message).to eq(new_message)
end end
@ -74,6 +87,7 @@ describe Chat::ChatMessageUpdater do
chat_message = create_chat_message(user1, "This will be changed", public_chat_channel) chat_message = create_chat_message(user1, "This will be changed", public_chat_channel)
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: new_content:
"this is a message with @system @mentions @#{user2.username} and @#{user3.username}", "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) chat_message = create_chat_message(user1, message, public_chat_channel)
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: message + " editedddd", new_content: message + " editedddd",
) )
@ -98,6 +113,7 @@ describe Chat::ChatMessageUpdater do
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: message + " @#{user_without_memberships.username}", 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) create_chat_message(user1, "ping @#{user2.username} @#{user3.username}", public_chat_channel)
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "ping @#{user3.username}", new_content: "ping @#{user3.username}",
) )
@ -119,6 +136,7 @@ describe Chat::ChatMessageUpdater do
chat_message = chat_message =
create_chat_message(user1, "ping @#{user2.username} @#{user3.username}", public_chat_channel) create_chat_message(user1, "ping @#{user2.username} @#{user3.username}", public_chat_channel)
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "ping @#{user3.username} @#{user4.username}", 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) chat_message = create_chat_message(user1, "ping nobody", @direct_message_channel)
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "ping @#{admin1.username}", new_content: "ping @#{admin1.username}",
) )
@ -143,6 +162,7 @@ describe Chat::ChatMessageUpdater do
chat_message = create_chat_message(user1, "ping nobody", public_chat_channel) chat_message = create_chat_message(user1, "ping nobody", public_chat_channel)
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "ping @#{admin_group.name}", 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) chat_message = create_chat_message(user1, "ping @#{admin2.username}", public_chat_channel)
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "ping @#{admin_group.name} @#{admin2.username}", 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) chat_message = create_chat_message(user1, "ping @#{admin_group.name}", public_chat_channel)
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "ping nobody anymore!", new_content: "ping nobody anymore!",
) )
@ -176,14 +198,20 @@ describe Chat::ChatMessageUpdater do
end end
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!" old_message = "It's a thrsday!"
new_message = "It's a thursday!" new_message = "It's a thursday!"
chat_message = create_chat_message(user1, old_message, public_chat_channel) 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 revision = chat_message.revisions.last
expect(revision.old_message).to eq(old_message) expect(revision.old_message).to eq(old_message)
expect(revision.new_message).to eq(new_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 end
describe "uploads" do describe "uploads" do
@ -200,6 +228,7 @@ describe Chat::ChatMessageUpdater do
) )
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "I guess this is different", new_content: "I guess this is different",
upload_ids: [upload2.id, upload1.id], upload_ids: [upload2.id, upload1.id],
@ -217,6 +246,7 @@ describe Chat::ChatMessageUpdater do
) )
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "I guess this is different", new_content: "I guess this is different",
upload_ids: [upload1.id], upload_ids: [upload1.id],
@ -234,6 +264,7 @@ describe Chat::ChatMessageUpdater do
) )
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "I guess this is different", new_content: "I guess this is different",
upload_ids: [], upload_ids: [],
@ -245,6 +276,7 @@ describe Chat::ChatMessageUpdater do
chat_message = create_chat_message(user1, "something", public_chat_channel) chat_message = create_chat_message(user1, "something", public_chat_channel)
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "I guess this is different", new_content: "I guess this is different",
upload_ids: [upload1.id], upload_ids: [upload1.id],
@ -256,6 +288,7 @@ describe Chat::ChatMessageUpdater do
chat_message = create_chat_message(user1, "something", public_chat_channel) chat_message = create_chat_message(user1, "something", public_chat_channel)
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "I guess this is different", new_content: "I guess this is different",
upload_ids: [upload1.id, upload2.id], 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) }.to change { ChatUpload.where(chat_message: chat_message).count }.by(2)
end 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 = chat_message =
create_chat_message(user1, "something", public_chat_channel, upload_ids: [upload1.id]) create_chat_message(user1, "something", public_chat_channel, upload_ids: [upload1.id])
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "I guess this is different", new_content: "I guess this is different",
upload_ids: [0], upload_ids: [0],
@ -280,6 +314,7 @@ describe Chat::ChatMessageUpdater do
chat_message = create_chat_message(user1, "something", public_chat_channel) chat_message = create_chat_message(user1, "something", public_chat_channel)
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "I guess this is different", new_content: "I guess this is different",
upload_ids: [upload1.id, upload2.id], upload_ids: [upload1.id, upload2.id],
@ -298,6 +333,7 @@ describe Chat::ChatMessageUpdater do
) )
expect { expect {
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: "I guess this is different", new_content: "I guess this is different",
upload_ids: [], upload_ids: [],
@ -316,6 +352,7 @@ describe Chat::ChatMessageUpdater do
SiteSetting.chat_minimum_message_length = 10 SiteSetting.chat_minimum_message_length = 10
new_message = "hi :)" new_message = "hi :)"
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: guardian,
chat_message: chat_message, chat_message: chat_message,
new_content: new_message, new_content: new_message,
upload_ids: [upload1.id], upload_ids: [upload1.id],
@ -348,6 +385,7 @@ describe Chat::ChatMessageUpdater do
def update_message(user) def update_message(user)
message.update(user: user) message.update(user: user)
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: Guardian.new(user),
chat_message: message, chat_message: message,
new_content: "I guess this is different", new_content: "I guess this is different",
) )

View File

@ -117,6 +117,7 @@ describe Chat::ChatReviewQueue do
it "ignores the cooldown window when the message is edited" do it "ignores the cooldown window when the message is edited" do
Chat::ChatMessageUpdater.update( Chat::ChatMessageUpdater.update(
guardian: Guardian.new(message.user),
chat_message: message, chat_message: message,
new_content: "I'm editing this message. Please flag it.", new_content: "I'm editing this message. Please flag it.",
) )