diff --git a/app/jobs/tag_alias_undo_job.rb b/app/jobs/tag_alias_undo_job.rb new file mode 100644 index 000000000..aba4397c3 --- /dev/null +++ b/app/jobs/tag_alias_undo_job.rb @@ -0,0 +1,8 @@ +class TagAliasJob < ApplicationJob + queue_as :tags + + def perform(*args) + ta = TagAlias.find(args[0]) + ta.process_undo!(update_topic: args[1]) + end +end diff --git a/app/jobs/tag_alias_update_posts_job.rb b/app/jobs/tag_alias_update_posts_job.rb deleted file mode 100644 index 84b21fce9..000000000 --- a/app/jobs/tag_alias_update_posts_job.rb +++ /dev/null @@ -1,9 +0,0 @@ -class TagAliasUpdatePostsJob < ApplicationJob - queue_as :tags - - def perform(*args) - ta = TagAlias.find(args[0]) - - ta.update_posts - end -end diff --git a/app/models/tag_alias.rb b/app/models/tag_alias.rb index 7e31b285b..cad1d302b 100644 --- a/app/models/tag_alias.rb +++ b/app/models/tag_alias.rb @@ -1,4 +1,6 @@ class TagAlias < TagRelationship + has_many :tag_rel_undos, as: :tag_rel + after_save :create_mod_action validates :antecedent_name, uniqueness: true validate :absence_of_transitive_relation @@ -9,9 +11,16 @@ class TagAlias < TagRelationship def approve!(update_topic: true, approver: CurrentUser.user) CurrentUser.scoped(approver) do update(status: "queued", approver_id: approver.id) + create_undo_information TagAliasJob.perform_later(id, update_topic) end end + + def undo!(approver: CurrentUser.user) + CurrentUser.scoped(approver) do + TagAliaseUndoJob.perform_later(id, true) + end + end end module ForumMethods @@ -54,7 +63,7 @@ class TagAlias < TagRelationship TagAlias.to_aliased_with_originals(names).values end - def self.to_aliased_query(query) + def self.to_aliased_query(query, overrides: nil) # Remove tag types (newline syntax) query.gsub!(/(^| )(-)?(#{TagCategory.mapping.keys.sort_by { |x| -x.size }.join("|")}):([\S])/i, '\1\2\4') # Remove tag types (comma syntax) @@ -66,11 +75,85 @@ class TagAlias < TagRelationship [negated ? x[1..-1] : x, negated] end aliased = to_aliased_with_originals(tags.map { |t| t[0] }) + aliased.merge!(overrides) if overrides tags.map { |t| "#{t[1] ? '-' : ''}#{aliased[t[0]]}" }.join(" ") end lines.uniq.join("\n") end + def process_undo!(update_topic: true) + unless valid? + raise errors.full_messages.join("; ") + end + + CurrentUser.scoped(approver) do + update(status: "pending") + update_posts_locked_tags_undo + update_blacklists_undo + update_posts_undo + forum_updater.update(retirement_message, "UNDONE") if update_topic + rename_wiki_and_artist_undo + end + tag_rel_undos.update_all(applied: true) + end + + def update_posts_locked_tags_undo + Post.without_timeout do + Post.where_ilike(:locked_tags, "*#{consequent_name}*").find_each(batch_size: 50) do |post| + fixed_tags = TagAlias.to_aliased_query(post.locked_tags, overrides: {consequent_name => antecedent_name}) + CurrentUser.scoped(creator, creator_ip_addr) do + post.update_column(:locked_tags, fixed_tags) + end + end + end + end + + def update_blacklists_undo + User.without_timeout do + User.where_ilike(:blacklisted_tags, "*#{consequent_name}*").find_each(batch_size: 50) do |user| + fixed_blacklist = TagAlias.to_aliased_query(user.blacklisted_tags, overrides: {consequent_name => antecedent_name}) + user.update_column(:blacklisted_tags, fixed_blacklist) + end + end + end + + def update_posts_undo + Post.without_timeout do + tag_rel_undos.where(applied: false).each do |tu| + Post.where(id: tu.undo_data).find_each do |post| + post.do_not_version_changes = true + post.tag_string_diff = "-#{consequent_name} #{antecedent_name}" + post.save + end + end + + # TODO: Race condition with indexing jobs here. + antecedent_tag.fix_post_count if antecedent_tag + consequent_tag.fix_post_count if consequent_tag + end + end + + def rename_wiki_and_artist_undo + consequent_wiki = WikiPage.titled(consequent_name).first + if consequent_wiki.present? + if WikiPage.titled(antecedent_name).blank? + CurrentUser.scoped(creator, creator_ip_addr) do + consequent_wiki.update(title: antecedent_name, skip_secondary_validations: true) + end + else + forum_updater.update(conflict_message) + end + end + + if consequent_tag.category == Tag.categories.artist + if consequent_tag.artist.present? && antecedent_tag.artist.blank? + CurrentUser.scoped(creator, creator_ip_addr) do + consequent_tag.artist.update!(name: antecedent_name) + end + end + end + end + def process!(update_topic: true) unless valid? raise errors.full_messages.join("; ") @@ -82,14 +165,13 @@ class TagAlias < TagRelationship CurrentUser.scoped(approver) do update(status: "processing") move_aliases_and_implications - move_saved_searches ensure_category_consistency update_posts_locked_tags update_blacklists update_posts forum_updater.update(approval_message(approver), "APPROVED") if update_topic rename_wiki_and_artist - update(status: "active", post_count: consequent_tag.post_count) + update(status: 'active', post_count: consequent_tag.post_count) end rescue Exception => e Rails.logger.error("[TA] #{e.message}\n#{e.backtrace}") @@ -116,17 +198,6 @@ class TagAlias < TagRelationship end end - def move_saved_searches - escaped = Regexp.escape(antecedent_name) - - if SavedSearch.enabled? - SavedSearch.where("query like ?", "%#{antecedent_name}%").find_each do |ss| - ss.query = ss.query.sub(/(?:^| )#{escaped}(?:$| )/, " #{consequent_name} ").strip.gsub(/ /, " ") - ss.save - end - end - end - def move_aliases_and_implications aliases = TagAlias.where(["consequent_name = ?", antecedent_name]) aliases.each do |ta| @@ -182,6 +253,18 @@ class TagAlias < TagRelationship end end + def create_undo_information + post_ids = [] + Post.transaction do + Post.without_timeout do + Post.sql_raw_tag_match(antecedent_name).find_each do |post| + post_ids << post.id + end + tag_rel_undos.create!(undo_data: post_ids) + end + end + end + def update_posts Post.without_timeout do Post.sql_raw_tag_match(antecedent_name).find_each do |post| diff --git a/app/models/tag_implication.rb b/app/models/tag_implication.rb index f2ec46411..68238c3fc 100644 --- a/app/models/tag_implication.rb +++ b/app/models/tag_implication.rb @@ -1,6 +1,8 @@ class TagImplication < TagRelationship extend Memoist + has_many :tag_rel_undos, as: :tag_rel + array_attribute :descendant_names before_save :update_descendant_names @@ -150,6 +152,19 @@ class TagImplication < TagRelationship end end + def create_undo_information + Post.without_timeout do + Post.sql_raw_tag_match(antecedent_name).find_in_batches do |posts| + post_info = Hash.new + posts.each do |p| + post_info[p.id] = p.tag_string + end + tag_rel_undos.create!(undo_data: post_info) + end + end + + end + def update_posts Post.without_timeout do Post.sql_raw_tag_match(antecedent_name).find_each do |post| @@ -165,6 +180,7 @@ class TagImplication < TagRelationship def approve!(approver: CurrentUser.user, update_topic: true) update(status: "queued", approver_id: approver.id) + create_undo_information TagImplicationJob.perform_later(id, update_topic) end diff --git a/app/models/tag_rel_undo.rb b/app/models/tag_rel_undo.rb new file mode 100644 index 000000000..256200df9 --- /dev/null +++ b/app/models/tag_rel_undo.rb @@ -0,0 +1,3 @@ +class TagRelUndo < ApplicationRecord + belongs_to :tag_rel, polymorphic: true +end diff --git a/db/migrate/20191003070653_create_tag_relationship_undos.rb b/db/migrate/20191003070653_create_tag_relationship_undos.rb new file mode 100644 index 000000000..f3805ff20 --- /dev/null +++ b/db/migrate/20191003070653_create_tag_relationship_undos.rb @@ -0,0 +1,10 @@ +class CreateTagRelationshipUndos < ActiveRecord::Migration[6.0] + def change + create_table :tag_rel_undos do |t| + t.references :tag_rel, polymorphic: true + t.json :undo_data + t.boolean :applied, default: false + t.timestamps + end + end +end diff --git a/db/structure.sql b/db/structure.sql index 02126dfe3..e5e4aaf9e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -2185,6 +2185,40 @@ CREATE SEQUENCE public.tag_implications_id_seq ALTER SEQUENCE public.tag_implications_id_seq OWNED BY public.tag_implications.id; +-- +-- Name: tag_rel_undos; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.tag_rel_undos ( + id bigint NOT NULL, + tag_rel_type character varying, + tag_rel_id bigint, + undo_data json, + applied boolean DEFAULT false, + created_at timestamp(6) without time zone NOT NULL, + updated_at timestamp(6) without time zone NOT NULL +); + + +-- +-- Name: tag_rel_undos_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE public.tag_rel_undos_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: tag_rel_undos_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE public.tag_rel_undos_id_seq OWNED BY public.tag_rel_undos.id; + + -- -- Name: tag_subscriptions; Type: TABLE; Schema: public; Owner: - -- @@ -3162,6 +3196,13 @@ ALTER TABLE ONLY public.tag_aliases ALTER COLUMN id SET DEFAULT nextval('public. ALTER TABLE ONLY public.tag_implications ALTER COLUMN id SET DEFAULT nextval('public.tag_implications_id_seq'::regclass); +-- +-- Name: tag_rel_undos id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tag_rel_undos ALTER COLUMN id SET DEFAULT nextval('public.tag_rel_undos_id_seq'::regclass); + + -- -- Name: tag_subscriptions id; Type: DEFAULT; Schema: public; Owner: - -- @@ -3708,6 +3749,14 @@ ALTER TABLE ONLY public.tag_implications ADD CONSTRAINT tag_implications_pkey PRIMARY KEY (id); +-- +-- Name: tag_rel_undos tag_rel_undos_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.tag_rel_undos + ADD CONSTRAINT tag_rel_undos_pkey PRIMARY KEY (id); + + -- -- Name: tag_subscriptions tag_subscriptions_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -4744,6 +4793,13 @@ CREATE INDEX index_tag_implications_on_consequent_name ON public.tag_implication CREATE INDEX index_tag_implications_on_forum_post_id ON public.tag_implications USING btree (forum_post_id); +-- +-- Name: index_tag_rel_undos_on_tag_rel_type_and_tag_rel_id; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_tag_rel_undos_on_tag_rel_type_and_tag_rel_id ON public.tag_rel_undos USING btree (tag_rel_type, tag_rel_id); + + -- -- Name: index_tag_subscriptions_on_creator_id; Type: INDEX; Schema: public; Owner: - -- @@ -5268,6 +5324,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20190905111159'), ('20190916204908'), ('20190919213915'), -('20190924233432'); +('20190924233432'), +('20191003070653');