diff --git a/app/controllers/tag_adjustments_controller.rb b/app/controllers/tag_adjustments_controller.rb new file mode 100644 index 000000000..6349edfab --- /dev/null +++ b/app/controllers/tag_adjustments_controller.rb @@ -0,0 +1,14 @@ +class TagAdjustmentsController < ApplicationController + def create + authorize(User, :moderation_routes?) + TagAdjustmentCreationService.new(current_user, { + adjustment_type: "removal", + status: "committed", + tag_name: params[:tag_adjustment][:tag_name], + article_id: params[:tag_adjustment][:article_id], + reason_for_adjustment: params[:tag_adjustment][:reason_for_adjustment] + }).create + @article = Article.find(params[:tag_adjustment][:article_id]) + redirect_to "#{@article.path}/mod" + end +end diff --git a/app/models/article.rb b/app/models/article.rb index 1b760db51..2796c92dd 100644 --- a/app/models/article.rb +++ b/app/models/article.rb @@ -403,6 +403,9 @@ class Article < ApplicationRecord ActsAsTaggableOn::Taggable::Cache.included(Article) self.tag_list = [] tag_list.add(front_matter["tags"], parser: ActsAsTaggableOn::TagParser) + TagAdjustment.where(article_id: id, adjustment_type: "removal", status: "committed").pluck(:tag_name).each do |name| + tag_list.remove(name, parser: ActsAsTaggableOn::TagParser) + end end self.published = front_matter["published"] if ["true", "false"].include?(front_matter["published"].to_s) self.published_at = parsed_date(front_matter["date"]) if published @@ -425,6 +428,11 @@ class Article < ApplicationRecord end def validate_tag + # remove adjusted tags + TagAdjustment.where(article_id: id, adjustment_type: "removal", status: "committed").pluck(:tag_name).each do |name| + tag_list.remove(name, parser: ActsAsTaggableOn::TagParser) + self.tag_list = tag_list + end return errors.add(:tag_list, "exceed the maximum of 4 tags") if tag_list.length > 4 tag_list.each do |tag| if tag.length > 20 diff --git a/app/models/notification.rb b/app/models/notification.rb index a0a975418..a7c8d462c 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -187,6 +187,26 @@ class Notification < ApplicationRecord end handle_asynchronously :send_moderation_notification + def send_tag_adjustment_notification(notifiable) + article = notifiable.article + json_data = { + article: {title: article.title, path: article.path}, + adjustment_type: notifiable.adjustment_type, + status: notifiable.status, + reason_for_adjustment: notifiable.reason_for_adjustment, + tag_name: notifiable.tag_name + } + Notification.create( + user_id: article.user_id, + notifiable_id: notifiable.id, + notifiable_type: notifiable.class.name, + json_data: json_data, + ) + article.user.update_column(:last_moderation_notification, Time.current) + end + handle_asynchronously :send_tag_adjustment_notification + + def remove_all(notifiable_hash) Notification.where( notifiable_id: notifiable_hash[:id], diff --git a/app/models/tag_adjustment.rb b/app/models/tag_adjustment.rb new file mode 100644 index 000000000..d548fd401 --- /dev/null +++ b/app/models/tag_adjustment.rb @@ -0,0 +1,23 @@ +class TagAdjustment < ApplicationRecord + + validates :user_id, presence: true + validates :article_id, presence: true + validates :tag_id, presence: true + validates :tag_name, presence: true, uniqueness: { scope: :article_id } + validates :reason_for_adjustment, presence: true + validates :adjustment_type, inclusion: { in: %w(removal addition) }, presence: true + validates :status, inclusion: { in: %w(committed pending committed_and_resolvable resolved) }, presence: true + validate :user_permissions + + belongs_to :user + belongs_to :tag + belongs_to :article + + private + + def user_permissions + unless user&.has_role?(:tag_moderator, tag) || user&.has_role?(:admin) || user&.has_role?(:super_admin) + errors.add(:user_id, "does not have privilege to adjust these tags") + end + end +end diff --git a/app/services/tag_adjustment_creation_service.rb b/app/services/tag_adjustment_creation_service.rb new file mode 100644 index 000000000..0fe55c8bf --- /dev/null +++ b/app/services/tag_adjustment_creation_service.rb @@ -0,0 +1,27 @@ +class TagAdjustmentCreationService + def initialize(user, tag_adjustment_params) + @user = user + @tag_adjustment_params = tag_adjustment_params + end + + def create + @tag_adjustment = TagAdjustment.create!(creation_args) + update_article + Notification.send_tag_adjustment_notification(@tag_adjustment) + @tag_adjustment + end + + private + + def update_article + article = Article.find(creation_args[:article_id]) + article.update!(tag_list: article.tag_list.remove(@tag_adjustment.tag_name)) if @tag_adjustment.adjustment_type == "removal" + end + + def creation_args + args = @tag_adjustment_params + args[:user_id] = @user.id + args[:tag_id] = Tag.find_by_name(args[:tag_name])&.id + args + end +end diff --git a/app/services/tag_adjustment_update_service.rb b/app/services/tag_adjustment_update_service.rb new file mode 100644 index 000000000..92430784f --- /dev/null +++ b/app/services/tag_adjustment_update_service.rb @@ -0,0 +1,13 @@ +class TagAdjustmentUpdateService + def initialize(tag_adjustment, tag_adjustment_params) + @tag_adjustment = tag_adjustment + @tag_adjustment_params = tag_adjustment_params + end + + def update + @tag_adjustment.update(@tag_adjustment_params) + # Overall incomplete + # We don't yet need this but will. + # Not supporting update of notification functionality yet + end +end diff --git a/app/views/moderations/mod.html.erb b/app/views/moderations/mod.html.erb index 83fee8ba1..3fa4d76b9 100644 --- a/app/views/moderations/mod.html.erb +++ b/app/views/moderations/mod.html.erb @@ -20,6 +20,41 @@ .reaction-button.reacted .reacted-emoji { display: block; } + .tag-mod-form { + border-top: 2px solid #dddddd; + margin: 40px auto; + } + .tag-mod-form ul { + text-align: left; + font-size: 15px; + } + .tag-mod-form form{ + width: 500px; + margin: auto; + max-width: 90%; + } + .tag-mod-form form input{ + width: 100%; + padding: 5px; + font-size: 20px; + margin-bottom: 5px; + border: 1px solid #888; + } + .tag-mod-form form input[type="submit"]{ + background: #0045ff; + color: white; + font-weight: bold; + padding: 10px; + margin: auto; + display: block; + } + .tag-mod-form form textarea{ + width: 100%; + padding: 5px; + font-size: 17px; + height: 50px; + border: 1px solid #888; + }
@@ -33,7 +68,6 @@ <%= image_tag "emoji/emoji-one-nausea-face-gray.png" %> "/> - <% if current_user.has_role?(:super_admin) && @moderatable.class.name == "Article" %>

Admin: Edit | Content Moderation | Article Admin

<% elsif current_user.has_role?(:super_admin) && @moderatable.class.name == "Comment" %> @@ -46,6 +80,36 @@

The DEV team will be notified about vomits and take appropriate action, but leaving a level-headed comment addressing the behavior is welcome if you feel up for it.

<% end %> + <% is_mod = (@moderatable.tag_list.select { |t| current_user.has_role?(:tag_moderator, Tag.find_by_name(t)) }).any? if @moderatable.class.name == "Article" %> + <% if @moderatable.class.name == "Article" && (current_user.has_role?(:super_admin) || is_mod) %> +
+ <%= form_for(TagAdjustment.new) do |f| %> +

Remove Innapropriate Tags

+ <% unless current_user.has_role?(:super_admin) %> + + <% end %> + Current live tags: <%= @moderatable.tag_list %> +

+ <%= f.hidden_field :article_id, value: @moderatable.id %> + <%= f.text_field :tag_name, placeholder: "Tag Name" %> + <%= f.text_area :reason_for_adjustment, placeholder: "Reason for Removal (Be super kind)" %> + <%= f.submit "Remove Tag" %> + <% adjustments = TagAdjustment.where(article_id: @moderatable.id, adjustment_type: "removal") %> + <% if adjustments.any? %> +

+ Removed tags: + <% adjustments.each do |adjustment| %> + <%= adjustment.tag_name %> + <% end %> + <% end %> + <% end %> +
+ <% end %>