2024-02-25 12:15:55 -05:00
# frozen_string_literal: true
2022-12-26 14:21:33 -05:00
class BulkUpdateRequestImporter
2018-02-24 15:37:02 -05:00
class Error < RuntimeError ; end
2022-12-26 14:36:53 -05:00
attr_accessor :text , :forum_id , :creator_id , :creator_ip_addr
2013-03-19 08:10:10 -04:00
2022-12-26 14:36:53 -05:00
def initialize ( text , forum_id , creator = nil , ip_addr = nil )
2011-12-20 11:46:07 -05:00
@forum_id = forum_id
@text = text
2020-07-13 01:53:02 -04:00
@creator_id = creator
@creator_ip_addr = ip_addr
2011-12-20 11:46:07 -05:00
end
2013-03-19 08:10:10 -04:00
2016-10-26 19:40:58 -04:00
def process! ( approver = CurrentUser . user )
2022-12-26 14:21:33 -05:00
tokens = BulkUpdateRequestImporter . tokenize ( text )
2020-07-13 01:53:02 -04:00
execute ( tokens , approver )
2011-12-20 11:46:07 -05:00
end
2013-03-19 08:10:10 -04:00
2022-04-09 07:43:51 -04:00
def validate! ( user )
2022-12-26 14:21:33 -05:00
tokens = BulkUpdateRequestImporter . tokenize ( text )
2022-04-09 07:43:51 -04:00
validate_annotate ( tokens , user )
2016-01-28 20:39:01 -05:00
end
2014-06-17 13:19:40 -04:00
def self . tokenize ( text )
2018-09-24 18:34:08 -04:00
text . split ( / \ r \ n| \ r| \ n / ) . reject ( & :blank? ) . map do | line |
2018-09-20 20:13:31 -04:00
line = line . gsub ( / [[:space:]]+ / , " " ) . strip
2020-07-13 01:53:02 -04:00
if line =~ / ^(?:create alias|aliasing|alias) ( \ S+) -> ( \ S+)( # .*)?$ /i
[ :create_alias , $1 , $2 , $3 ]
2015-07-29 21:00:41 -04:00
2020-07-13 01:53:02 -04:00
elsif line =~ / ^(?:create implication|implicating|implicate|imply) ( \ S+) -> ( \ S+)( # .*)?$ /i
[ :create_implication , $1 , $2 , $3 ]
2015-07-29 21:00:41 -04:00
2020-07-13 01:53:02 -04:00
elsif line =~ / ^(?:remove alias|unaliasing|unalias) ( \ S+) -> ( \ S+)( # .*)?$ /i
[ :remove_alias , $1 , $2 , $3 ]
2015-07-29 21:00:41 -04:00
2020-07-13 01:53:02 -04:00
elsif line =~ / ^(?:remove implication|unimplicating|unimplicate|unimply) ( \ S+) -> ( \ S+)( # .*)?$ /i
[ :remove_implication , $1 , $2 , $3 ]
2015-07-29 21:00:41 -04:00
2022-03-05 11:01:25 -05:00
elsif line =~ / ^(?:mass update|updating|update|change) ( \ S+) -> ( \ S+)( # .*)?$ /i
2020-07-13 01:53:02 -04:00
[ :mass_update , $1 , $2 , $3 ]
2015-07-29 21:00:41 -04:00
2022-10-01 15:37:00 -04:00
elsif line =~ / ^(?:nuke tag|nuke) ( \ S+)( # .*)?$ /i
[ :nuke_tag , $1 , nil , $2 ]
2020-07-13 01:53:02 -04:00
elsif line =~ / ^category ( \ S+) -> ( #{ Tag . categories . regexp } )( # .*)?$ /i
[ :change_category , $1 , $2 , $3 ]
2017-11-16 15:00:54 -05:00
2015-04-21 21:39:42 -04:00
elsif line . strip . empty?
2011-12-20 11:46:07 -05:00
# do nothing
2017-11-16 15:00:54 -05:00
2011-12-20 11:46:07 -05:00
else
2018-02-24 15:37:02 -05:00
raise Error , " Unparseable line: #{ line } "
2011-12-20 11:46:07 -05:00
end
end
end
2013-03-19 08:10:10 -04:00
2020-07-13 01:53:02 -04:00
def self . untokenize ( tokens )
2016-01-28 20:39:01 -05:00
tokens . map do | token |
case token [ 0 ]
when :create_alias
2024-03-25 20:43:21 -04:00
comment = " # #{ token [ 3 ] } " if token [ 3 ] . present?
2020-07-13 01:53:02 -04:00
" alias #{ token [ 1 ] } -> #{ token [ 2 ] } #{ comment } " . strip
when :create_implication
2024-03-25 20:43:21 -04:00
comment = " # #{ token [ 3 ] } " if token [ 3 ] . present?
2020-07-13 01:53:02 -04:00
" implicate #{ token [ 1 ] } -> #{ token [ 2 ] } #{ comment } " . strip
when :remove_alias
comment = " # missing " if token [ 3 ] == false
" unalias #{ token [ 1 ] } -> #{ token [ 2 ] } #{ comment } " . strip
when :remove_implication
comment = " # missing " if token [ 3 ] == false
" unimplicate #{ token [ 1 ] } -> #{ token [ 2 ] } #{ comment } " . strip
when :change_category
2022-03-05 11:01:25 -05:00
comment = " # missing " if token [ 3 ] == false
" category #{ token [ 1 ] } -> #{ token [ 2 ] } #{ comment } " . strip
2020-07-13 01:53:02 -04:00
when :mass_update
2022-03-05 11:01:25 -05:00
comment = " # missing " if token [ 3 ] == false
" update #{ token [ 1 ] } -> #{ token [ 2 ] } #{ comment } " . strip
2022-10-01 15:37:00 -04:00
when :nuke_tag
comment = " # missing " if token [ 3 ] == false
" nuke tag #{ token [ 1 ] } #{ comment } " . strip
2020-07-13 01:53:02 -04:00
else
raise Error . new ( " Unknown token to reverse " )
end
end
end
def validate_alias ( token )
tag_alias = TagAlias . duplicate_relevant . find_by ( antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] )
2020-07-18 09:25:44 -04:00
if tag_alias . present? && tag_alias . has_transitives
2024-03-25 20:43:21 -04:00
return [ nil , " duplicate of alias # #{ tag_alias . id } ; has blocking transitive relationships, cannot be applied through BUR " ]
2020-07-15 09:05:46 -04:00
end
2024-03-25 20:43:21 -04:00
return [ nil , " duplicate of alias # #{ tag_alias . id } " ] unless tag_alias . nil?
tag_alias = TagAlias . new ( forum_topic_id : forum_id , status : " pending " , antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] )
2020-07-13 01:53:02 -04:00
unless tag_alias . valid?
return [ " Error: #{ tag_alias . errors . full_messages . join ( " ; " ) } (create alias #{ tag_alias . antecedent_name } -> #{ tag_alias . consequent_name } ) " , nil ]
end
2020-07-15 09:05:46 -04:00
if tag_alias . has_transitives
return [ nil , " has blocking transitive relationships, cannot be applied through BUR " ]
end
2020-07-13 01:53:02 -04:00
return [ nil , nil ]
end
def validate_implication ( token )
tag_implication = TagImplication . duplicate_relevant . find_by ( antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] )
2024-03-25 20:43:21 -04:00
return [ nil , " duplicate of implication # #{ tag_implication . id } " ] unless tag_implication . nil?
tag_implication = TagImplication . new ( forum_topic_id : forum_id , status : " pending " , antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] )
2020-07-13 01:53:02 -04:00
unless tag_implication . valid?
2020-07-13 12:46:15 -04:00
return [ " Error: #{ tag_implication . errors . full_messages . join ( " ; " ) } (create implication #{ tag_implication . antecedent_name } -> #{ tag_implication . consequent_name } ) " , nil ]
2020-07-13 01:53:02 -04:00
end
return [ nil , nil ]
end
2022-04-09 07:43:51 -04:00
def validate_annotate ( tokens , user )
2020-07-13 01:53:02 -04:00
errors = [ ]
annotated = tokens . map do | token |
case token [ 0 ]
when :create_alias
output = validate_alias ( token )
errors << output [ 0 ] if output [ 0 ] . present?
token [ 3 ] = output [ 1 ]
token
2016-01-28 20:39:01 -05:00
when :create_implication
2020-07-13 01:53:02 -04:00
output = validate_implication ( token )
errors << output [ 0 ] if output [ 0 ] . present?
token [ 3 ] = output [ 1 ]
token
when :remove_alias
existing = TagAlias . duplicate_relevant . find_by ( antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] ) . present?
token [ 3 ] = existing
token
2016-01-28 20:39:01 -05:00
2020-07-13 01:53:02 -04:00
when :remove_implication
existing = TagImplication . duplicate_relevant . find_by ( antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] ) . present?
token [ 3 ] = existing
token
when :mass_update , :change_category
2022-03-05 11:01:25 -05:00
existing = Tag . find_by ( name : token [ 1 ] ) . present?
token [ 3 ] = existing
2020-07-13 01:53:02 -04:00
token
2016-01-28 20:39:01 -05:00
2022-10-01 15:37:00 -04:00
when :nuke_tag
2022-10-02 15:35:21 -04:00
errors << " Only admins can nuke tags " unless user . is_admin?
2022-10-01 15:37:00 -04:00
existing = Tag . find_by ( name : token [ 1 ] ) . present?
token [ 3 ] = existing
token
2016-01-28 20:39:01 -05:00
else
2020-07-13 01:53:02 -04:00
errors << " Unknown token: #{ token [ 0 ] } "
2016-01-28 20:39:01 -05:00
end
end
2022-04-09 07:43:51 -04:00
errors << " Cannot create BUR with more than 25 entries " if tokens . size > 25 && ! user . is_admin?
2022-12-26 14:21:33 -05:00
[ errors , BulkUpdateRequestImporter . untokenize ( annotated ) . join ( " \n " ) ]
2016-01-28 20:39:01 -05:00
end
2019-01-09 18:54:55 -05:00
def estimate_update_count
tokens = self . class . tokenize ( text )
tokens . inject ( 0 ) do | sum , token |
case token [ 0 ]
when :create_alias
sum + TagAlias . new ( antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] ) . estimate_update_count
when :create_implication
sum + TagImplication . new ( antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] ) . estimate_update_count
2022-10-01 15:37:00 -04:00
when :change_category , :mass_update , :nuke_tag
sum + ( Tag . find_by ( name : token [ 1 ] ) . try ( :post_count ) || 0 )
2019-01-09 18:54:55 -05:00
else
sum + 0
end
end
end
2022-04-07 12:14:15 -04:00
private
2014-06-17 13:19:40 -04:00
2020-07-13 01:53:02 -04:00
## These functions will find and appropriate existing aliases or implications if needed. This reduces friction with accepting
# a BUR, and makes it much easier to work with.
def find_create_alias ( token , approver )
tag_alias = TagAlias . duplicate_relevant . find_by ( antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] )
if tag_alias . present?
return unless tag_alias . status == 'pending'
2022-01-31 11:22:47 -05:00
tag_alias . update_columns ( creator_id : creator_id , creator_ip_addr : creator_ip_addr , forum_topic_id : forum_id )
2020-07-13 01:53:02 -04:00
else
2022-01-31 11:22:47 -05:00
tag_alias = TagAlias . create ( :forum_topic_id = > forum_id , :status = > " pending " , :antecedent_name = > token [ 1 ] , :consequent_name = > token [ 2 ] )
2020-07-13 01:53:02 -04:00
unless tag_alias . valid?
raise Error , " Error: #{ tag_alias . errors . full_messages . join ( " ; " ) } (create alias #{ tag_alias . antecedent_name } -> #{ tag_alias . consequent_name } ) "
end
end
2022-12-26 14:36:53 -05:00
tag_alias . rename_artist
2020-07-15 09:05:46 -04:00
raise Error , " Error: Alias would modify other aliases or implications through transitive relationships. (create alias #{ tag_alias . antecedent_name } -> #{ tag_alias . consequent_name } ) " if tag_alias . has_transitives
2024-02-17 06:12:39 -05:00
tag_alias . approve! ( approver : approver , update_topic : false )
2020-07-13 01:53:02 -04:00
end
def find_create_implication ( token , approver )
tag_implication = TagImplication . duplicate_relevant . find_by ( antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] )
if tag_implication . present?
return unless tag_implication . status == 'pending'
2022-01-31 11:22:47 -05:00
tag_implication . update_columns ( creator_id : creator_id , creator_ip_addr : creator_ip_addr , forum_topic_id : forum_id )
2020-07-13 01:53:02 -04:00
else
2022-01-31 11:22:47 -05:00
tag_implication = TagImplication . create ( :forum_topic_id = > forum_id , :status = > " pending " , :antecedent_name = > token [ 1 ] , :consequent_name = > token [ 2 ] )
2020-07-13 01:53:02 -04:00
unless tag_implication . valid?
raise Error , " Error: #{ tag_implication . errors . full_messages . join ( " ; " ) } (create implication #{ tag_implication . antecedent_name } -> #{ tag_implication . consequent_name } ) "
end
end
tag_implication . approve! ( approver : approver , update_topic : false )
end
def execute ( tokens , approver )
warnings = [ ]
2011-12-20 11:46:07 -05:00
ActiveRecord :: Base . transaction do
tokens . map do | token |
case token [ 0 ]
when :create_alias
2020-07-13 01:53:02 -04:00
find_create_alias ( token , approver )
2013-03-19 08:10:10 -04:00
2011-12-20 11:46:07 -05:00
when :create_implication
2020-07-13 01:53:02 -04:00
find_create_implication ( token , approver )
2013-03-19 08:10:10 -04:00
2011-12-20 11:46:07 -05:00
when :remove_alias
2019-01-03 17:30:39 -05:00
tag_alias = TagAlias . active . find_by ( antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] )
2018-02-24 15:37:02 -05:00
raise Error , " Alias for #{ token [ 1 ] } not found " if tag_alias . nil?
2018-12-31 18:07:14 -05:00
tag_alias . reject! ( update_topic : false )
2013-03-19 08:10:10 -04:00
2011-12-20 11:46:07 -05:00
when :remove_implication
2019-01-03 17:30:39 -05:00
tag_implication = TagImplication . active . find_by ( antecedent_name : token [ 1 ] , consequent_name : token [ 2 ] )
2018-02-24 15:37:02 -05:00
raise Error , " Implication for #{ token [ 1 ] } not found " if tag_implication . nil?
2018-12-31 18:07:14 -05:00
tag_implication . reject! ( update_topic : false )
2013-03-19 08:10:10 -04:00
2014-06-11 19:53:24 -04:00
when :mass_update
2019-02-17 05:01:12 -05:00
TagBatchJob . perform_later ( token [ 1 ] , token [ 2 ] , CurrentUser . id , CurrentUser . ip_addr )
2017-11-16 15:00:54 -05:00
2022-10-01 15:37:00 -04:00
when :nuke_tag
TagNukeJob . perform_later ( token [ 1 ] , CurrentUser . id , CurrentUser . ip_addr )
2017-11-16 15:00:54 -05:00
when :change_category
2022-03-05 11:01:25 -05:00
tag = Tag . find_by ( name : token [ 1 ] )
raise Error , " Tag for #{ token [ 1 ] } not found " if tag . nil?
2017-11-16 15:00:54 -05:00
tag . category = Tag . categories . value_for ( token [ 2 ] )
tag . save
2014-06-11 19:53:24 -04:00
2011-12-20 11:46:07 -05:00
else
2018-02-24 15:37:02 -05:00
raise Error , " Unknown token: #{ token [ 0 ] } "
2011-12-20 11:46:07 -05:00
end
end
end
end
end