90 lines
2.7 KiB
Ruby
90 lines
2.7 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
module Jobs
|
||
|
class ChangeDisplayName < ::Jobs::Base
|
||
|
sidekiq_options queue: "low"
|
||
|
|
||
|
# Avoid race conditions if a user's name is updated several times
|
||
|
# in quick succession.
|
||
|
cluster_concurrency 1
|
||
|
|
||
|
def execute(args)
|
||
|
@user = User.find_by(id: args[:user_id])
|
||
|
|
||
|
return unless user.present?
|
||
|
|
||
|
# We need to account for the case where the instance allows
|
||
|
# name to be empty by falling back to username.
|
||
|
@old_display_name = (args[:old_name].presence || user.username).unicode_normalize
|
||
|
@new_display_name = (args[:new_name].presence || user.username).unicode_normalize
|
||
|
|
||
|
@quote_rewriter = QuoteRewriter.new(user.id)
|
||
|
|
||
|
update_posts
|
||
|
update_revisions
|
||
|
end
|
||
|
|
||
|
private
|
||
|
|
||
|
attr_reader :user, :old_display_name, :new_display_name, :quote_rewriter
|
||
|
|
||
|
def update_posts
|
||
|
Post
|
||
|
.with_deleted
|
||
|
.joins(quoted("posts.id"))
|
||
|
.where("p.user_id = :user_id", user_id: user.id)
|
||
|
.find_each { |post| update_post(post) }
|
||
|
end
|
||
|
|
||
|
def update_revisions
|
||
|
PostRevision
|
||
|
.joins(quoted("post_revisions.post_id"))
|
||
|
.where("p.user_id = :user_id", user_id: user.id)
|
||
|
.find_each { |revision| update_revision(revision) }
|
||
|
end
|
||
|
|
||
|
def quoted(post_id_column)
|
||
|
<<~SQL
|
||
|
JOIN quoted_posts AS q ON (q.post_id = #{post_id_column})
|
||
|
JOIN posts AS p ON (q.quoted_post_id = p.id)
|
||
|
SQL
|
||
|
end
|
||
|
|
||
|
def update_post(post)
|
||
|
post.raw = update_raw(post.raw)
|
||
|
post.cooked = update_cooked(post.cooked)
|
||
|
|
||
|
post.update_columns(raw: post.raw, cooked: post.cooked)
|
||
|
|
||
|
SearchIndexer.index(post, force: true) if post.topic
|
||
|
rescue => e
|
||
|
Discourse.warn_exception(e, message: "Failed to update post with id #{post.id}")
|
||
|
end
|
||
|
|
||
|
def update_revision(revision)
|
||
|
if revision.modifications["raw"] || revision.modifications["cooked"]
|
||
|
revision.modifications["raw"].map! { |raw| update_raw(raw) }
|
||
|
revision.modifications["cooked"].map! { |cooked| update_cooked(cooked) }
|
||
|
revision.save!
|
||
|
end
|
||
|
rescue => e
|
||
|
Discourse.warn_exception(e, message: "Failed to update post revision with id #{revision.id}")
|
||
|
end
|
||
|
|
||
|
def update_raw(raw)
|
||
|
@quote_rewriter.rewrite_raw_display_name(raw, old_display_name, new_display_name)
|
||
|
end
|
||
|
|
||
|
# Uses Nokogiri instead of rebake, because it works for posts and revisions
|
||
|
# and there is no reason to invalidate oneboxes, run the post analyzer etc.
|
||
|
# when only the display name changes.
|
||
|
def update_cooked(cooked)
|
||
|
doc = Nokogiri::HTML5.fragment(cooked)
|
||
|
|
||
|
@quote_rewriter.rewrite_cooked_display_name(doc, old_display_name, new_display_name)
|
||
|
|
||
|
doc.to_html
|
||
|
end
|
||
|
end
|
||
|
end
|