From 74ead7d10698c7f18ca22c07d2f04ff81f419097 Mon Sep 17 00:00:00 2001
From: Takeshi Umeda <noel.yoshiba@gmail.com>
Date: Sun, 23 Oct 2022 01:30:55 +0900
Subject: [PATCH] Change featured tag updates to add/remove activity (#19409)

* Change featured tag updates to add/remove activity

* Fix to check for the existence of feature tag

* Rename service and worker

* Merge AddHashtagSerializer with AddSerializer

* Undo removal of sidekiq_options
---
 .../api/v1/featured_tags_controller.rb        |  8 +++----
 .../settings/featured_tags_controller.rb      | 13 ++++++-----
 app/models/featured_tag.rb                    |  4 ++++
 app/serializers/activitypub/add_serializer.rb | 23 +++++++++++++++++--
 .../activitypub/hashtag_serializer.rb         |  2 ++
 .../activitypub/remove_serializer.rb          | 23 +++++++++++++++++--
 app/services/create_featured_tag_service.rb   | 21 +++++++++++++++++
 app/services/remove_featured_tag_service.rb   | 18 +++++++++++++++
 .../account_raw_distribution_worker.rb        |  9 ++++++++
 app/workers/remove_featured_tag_worker.rb     | 11 +++++++++
 10 files changed, 117 insertions(+), 15 deletions(-)
 create mode 100644 app/services/create_featured_tag_service.rb
 create mode 100644 app/services/remove_featured_tag_service.rb
 create mode 100644 app/workers/activitypub/account_raw_distribution_worker.rb
 create mode 100644 app/workers/remove_featured_tag_worker.rb

diff --git a/app/controllers/api/v1/featured_tags_controller.rb b/app/controllers/api/v1/featured_tags_controller.rb
index a67db7040a..edb42a94ea 100644
--- a/app/controllers/api/v1/featured_tags_controller.rb
+++ b/app/controllers/api/v1/featured_tags_controller.rb
@@ -13,14 +13,12 @@ class Api::V1::FeaturedTagsController < Api::BaseController
   end
 
   def create
-    @featured_tag = current_account.featured_tags.create!(featured_tag_params)
-    ActivityPub::UpdateDistributionWorker.perform_in(3.minutes, current_account.id)
-    render json: @featured_tag, serializer: REST::FeaturedTagSerializer
+    featured_tag = CreateFeaturedTagService.new.call(current_account, featured_tag_params[:name])
+    render json: featured_tag, serializer: REST::FeaturedTagSerializer
   end
 
   def destroy
-    @featured_tag.destroy!
-    ActivityPub::UpdateDistributionWorker.perform_in(3.minutes, current_account.id)
+    RemoveFeaturedTagWorker.perform_async(current_account.id, @featured_tag.id)
     render_empty
   end
 
diff --git a/app/controllers/settings/featured_tags_controller.rb b/app/controllers/settings/featured_tags_controller.rb
index ae714e9125..cc6ec08438 100644
--- a/app/controllers/settings/featured_tags_controller.rb
+++ b/app/controllers/settings/featured_tags_controller.rb
@@ -10,10 +10,8 @@ class Settings::FeaturedTagsController < Settings::BaseController
   end
 
   def create
-    @featured_tag = current_account.featured_tags.new(featured_tag_params)
-
-    if @featured_tag.save
-      ActivityPub::UpdateDistributionWorker.perform_in(3.minutes, current_account.id)
+    if !featured_tag_exists?
+      CreateFeaturedTagService.new.call(current_account, featured_tag_params[:name])
       redirect_to settings_featured_tags_path
     else
       set_featured_tags
@@ -24,13 +22,16 @@ class Settings::FeaturedTagsController < Settings::BaseController
   end
 
   def destroy
-    @featured_tag.destroy!
-    ActivityPub::UpdateDistributionWorker.perform_in(3.minutes, current_account.id)
+    RemoveFeaturedTagWorker.perform_async(current_account.id, @featured_tag.id)
     redirect_to settings_featured_tags_path
   end
 
   private
 
+  def featured_tag_exists?
+    current_account.featured_tags.by_name(featured_tag_params[:name]).exists?
+  end
+
   def set_featured_tag
     @featured_tag = current_account.featured_tags.find(params[:id])
   end
diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb
index ec234a6fd9..3f5cddce6f 100644
--- a/app/models/featured_tag.rb
+++ b/app/models/featured_tag.rb
@@ -30,6 +30,10 @@ class FeaturedTag < ApplicationRecord
 
   LIMIT = 10
 
+  def sign?
+    true
+  end
+
   def name
     tag_id.present? ? tag.name : @name
   end
diff --git a/app/serializers/activitypub/add_serializer.rb b/app/serializers/activitypub/add_serializer.rb
index 6f5aab17f9..436b05086f 100644
--- a/app/serializers/activitypub/add_serializer.rb
+++ b/app/serializers/activitypub/add_serializer.rb
@@ -1,10 +1,29 @@
 # frozen_string_literal: true
 
 class ActivityPub::AddSerializer < ActivityPub::Serializer
+  class UriSerializer < ActiveModel::Serializer
+    include RoutingHelper
+
+    def serializable_hash(*_args)
+      ActivityPub::TagManager.instance.uri_for(object)
+    end
+  end
+
+  def self.serializer_for(model, options)
+    case model.class.name
+    when 'Status'
+      UriSerializer
+    when 'FeaturedTag'
+      ActivityPub::HashtagSerializer
+    else
+      super
+    end
+  end
+
   include RoutingHelper
 
   attributes :type, :actor, :target
-  attribute :proper_object, key: :object
+  has_one :proper_object, key: :object
 
   def type
     'Add'
@@ -15,7 +34,7 @@ class ActivityPub::AddSerializer < ActivityPub::Serializer
   end
 
   def proper_object
-    ActivityPub::TagManager.instance.uri_for(object)
+    object
   end
 
   def target
diff --git a/app/serializers/activitypub/hashtag_serializer.rb b/app/serializers/activitypub/hashtag_serializer.rb
index 90929c57f9..2b24eb8cc1 100644
--- a/app/serializers/activitypub/hashtag_serializer.rb
+++ b/app/serializers/activitypub/hashtag_serializer.rb
@@ -1,6 +1,8 @@
 # frozen_string_literal: true
 
 class ActivityPub::HashtagSerializer < ActivityPub::Serializer
+  context_extensions :hashtag
+
   include RoutingHelper
 
   attributes :type, :href, :name
diff --git a/app/serializers/activitypub/remove_serializer.rb b/app/serializers/activitypub/remove_serializer.rb
index 7fefda59da..fb224f8a99 100644
--- a/app/serializers/activitypub/remove_serializer.rb
+++ b/app/serializers/activitypub/remove_serializer.rb
@@ -1,10 +1,29 @@
 # frozen_string_literal: true
 
 class ActivityPub::RemoveSerializer < ActivityPub::Serializer
+  class UriSerializer < ActiveModel::Serializer
+    include RoutingHelper
+
+    def serializable_hash(*_args)
+      ActivityPub::TagManager.instance.uri_for(object)
+    end
+  end
+
+  def self.serializer_for(model, options)
+    case model.class.name
+    when 'Status'
+      UriSerializer
+    when 'FeaturedTag'
+      ActivityPub::HashtagSerializer
+    else
+      super
+    end
+  end
+
   include RoutingHelper
 
   attributes :type, :actor, :target
-  attribute :proper_object, key: :object
+  has_one :proper_object, key: :object
 
   def type
     'Remove'
@@ -15,7 +34,7 @@ class ActivityPub::RemoveSerializer < ActivityPub::Serializer
   end
 
   def proper_object
-    ActivityPub::TagManager.instance.uri_for(object)
+    object
   end
 
   def target
diff --git a/app/services/create_featured_tag_service.rb b/app/services/create_featured_tag_service.rb
new file mode 100644
index 0000000000..c99d16113b
--- /dev/null
+++ b/app/services/create_featured_tag_service.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class CreateFeaturedTagService < BaseService
+  include Payloadable
+
+  def call(account, name)
+    @account = account
+
+    FeaturedTag.create!(account: account, name: name).tap do |featured_tag|
+      ActivityPub::AccountRawDistributionWorker.perform_async(build_json(featured_tag), account.id) if @account.local?
+    end
+  rescue ActiveRecord::RecordNotUnique
+    FeaturedTag.by_name(name).find_by!(account: account)
+  end
+
+  private
+
+  def build_json(featured_tag)
+    Oj.dump(serialize_payload(featured_tag, ActivityPub::AddSerializer, signer: @account))
+  end
+end
diff --git a/app/services/remove_featured_tag_service.rb b/app/services/remove_featured_tag_service.rb
new file mode 100644
index 0000000000..2aa70e8fc6
--- /dev/null
+++ b/app/services/remove_featured_tag_service.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class RemoveFeaturedTagService < BaseService
+  include Payloadable
+
+  def call(account, featured_tag)
+    @account = account
+
+    featured_tag.destroy!
+    ActivityPub::AccountRawDistributionWorker.perform_async(build_json(featured_tag), account.id) if @account.local?
+  end
+
+  private
+
+  def build_json(featured_tag)
+    Oj.dump(serialize_payload(featured_tag, ActivityPub::RemoveSerializer, signer: @account))
+  end
+end
diff --git a/app/workers/activitypub/account_raw_distribution_worker.rb b/app/workers/activitypub/account_raw_distribution_worker.rb
new file mode 100644
index 0000000000..a84c7d2142
--- /dev/null
+++ b/app/workers/activitypub/account_raw_distribution_worker.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class ActivityPub::AccountRawDistributionWorker < ActivityPub::RawDistributionWorker
+  protected
+
+  def inboxes
+    @inboxes ||= AccountReachFinder.new(@account).inboxes
+  end
+end
diff --git a/app/workers/remove_featured_tag_worker.rb b/app/workers/remove_featured_tag_worker.rb
new file mode 100644
index 0000000000..065ec79d81
--- /dev/null
+++ b/app/workers/remove_featured_tag_worker.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class RemoveFeaturedTagWorker
+  include Sidekiq::Worker
+
+  def perform(account_id, featured_tag_id)
+    RemoveFeaturedTagService.new.call(Account.find(account_id), FeaturedTag.find(featured_tag_id))
+  rescue ActiveRecord::RecordNotFound
+    true
+  end
+end
-- 
GitLab