# 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 if user.blank? # 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