From cedcece0ccba626d97a910f2e3fecf93c2729ca4 Mon Sep 17 00:00:00 2001 From: Claire Date: Wed, 5 Oct 2022 00:16:40 +0200 Subject: Fix deleted pinned posts potentially counting towards the pinned posts limit (#19005) Fixes #18938 --- app/services/remove_status_service.rb | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/services') diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 8dc521eed..f9fdea2cb 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -21,6 +21,8 @@ class RemoveStatusService < BaseService with_lock("distribute:#{@status.id}") do @status.discard + StatusPin.find_by(status: @status)&.destroy + remove_from_self if @account.local? remove_from_followers remove_from_lists -- cgit From b0e3f0312c3271a2705f912602fcba70f4ed8b69 Mon Sep 17 00:00:00 2001 From: Takeshi Umeda Date: Thu, 20 Oct 2022 16:15:52 +0900 Subject: Add synchronization of remote featured tags (#19380) * Add LIMIT of featured tag to instance API response * Add featured_tags_collection_url to Account * Add synchronization of remote featured tags * Deliver update activity when updating featured tag * Remove featured_tags_collection_url * Revert "Add featured_tags_collection_url to Account" This reverts commit cff349fc27b104ded2df6bb5665132dc24dab09c. * Add hashtag sync from featured collections * Fix tag name normalize * Add target option to fetch featured collection * Refactor fetch_featured_tags_collection_service * Add LIMIT of featured tag to v1/instance API response --- app/controllers/api/v1/featured_tags_controller.rb | 2 + .../settings/featured_tags_controller.rb | 2 + app/models/featured_tag.rb | 4 +- app/serializers/rest/instance_serializer.rb | 4 ++ app/serializers/rest/v1/instance_serializer.rb | 4 ++ .../fetch_featured_collection_service.rb | 31 ++++++++- .../fetch_featured_tags_collection_service.rb | 78 ++++++++++++++++++++++ .../activitypub/process_account_service.rb | 7 +- .../synchronize_featured_collection_worker.rb | 6 +- .../synchronize_featured_tags_collection_worker.rb | 13 ++++ .../activitypub/update_distribution_worker.rb | 2 + 11 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 app/services/activitypub/fetch_featured_tags_collection_service.rb create mode 100644 app/workers/activitypub/synchronize_featured_tags_collection_worker.rb (limited to 'app/services') diff --git a/app/controllers/api/v1/featured_tags_controller.rb b/app/controllers/api/v1/featured_tags_controller.rb index c1ead4f54..a67db7040 100644 --- a/app/controllers/api/v1/featured_tags_controller.rb +++ b/app/controllers/api/v1/featured_tags_controller.rb @@ -14,11 +14,13 @@ class Api::V1::FeaturedTagsController < Api::BaseController 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 end def destroy @featured_tag.destroy! + ActivityPub::UpdateDistributionWorker.perform_in(3.minutes, current_account.id) render_empty end diff --git a/app/controllers/settings/featured_tags_controller.rb b/app/controllers/settings/featured_tags_controller.rb index aadff7c83..ae714e912 100644 --- a/app/controllers/settings/featured_tags_controller.rb +++ b/app/controllers/settings/featured_tags_controller.rb @@ -13,6 +13,7 @@ class Settings::FeaturedTagsController < Settings::BaseController @featured_tag = current_account.featured_tags.new(featured_tag_params) if @featured_tag.save + ActivityPub::UpdateDistributionWorker.perform_in(3.minutes, current_account.id) redirect_to settings_featured_tags_path else set_featured_tags @@ -24,6 +25,7 @@ class Settings::FeaturedTagsController < Settings::BaseController def destroy @featured_tag.destroy! + ActivityPub::UpdateDistributionWorker.perform_in(3.minutes, current_account.id) redirect_to settings_featured_tags_path end diff --git a/app/models/featured_tag.rb b/app/models/featured_tag.rb index 201ce75f5..b16c79ac7 100644 --- a/app/models/featured_tag.rb +++ b/app/models/featured_tag.rb @@ -26,6 +26,8 @@ class FeaturedTag < ApplicationRecord attr_writer :name + LIMIT = 10 + def name tag_id.present? ? tag.name : @name end @@ -50,7 +52,7 @@ class FeaturedTag < ApplicationRecord end def validate_featured_tags_limit - errors.add(:base, I18n.t('featured_tags.errors.limit')) if account.featured_tags.count >= 10 + errors.add(:base, I18n.t('featured_tags.errors.limit')) if account.featured_tags.count >= LIMIT end def validate_tag_name diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index dfa8ce40a..7d00b20ba 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -47,6 +47,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer streaming: Rails.configuration.x.streaming_api_base_url, }, + accounts: { + max_featured_tags: FeaturedTag::LIMIT, + }, + statuses: { max_characters: StatusLengthValidator::MAX_CHARS, max_media_attachments: 4, diff --git a/app/serializers/rest/v1/instance_serializer.rb b/app/serializers/rest/v1/instance_serializer.rb index 872175451..99d1b2bd6 100644 --- a/app/serializers/rest/v1/instance_serializer.rb +++ b/app/serializers/rest/v1/instance_serializer.rb @@ -58,6 +58,10 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer def configuration { + accounts: { + max_featured_tags: FeaturedTag::LIMIT, + }, + statuses: { max_characters: StatusLengthValidator::MAX_CHARS, max_media_attachments: 4, diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb index 37d05e055..026fe24c5 100644 --- a/app/services/activitypub/fetch_featured_collection_service.rb +++ b/app/services/activitypub/fetch_featured_collection_service.rb @@ -3,10 +3,11 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService include JsonLdHelper - def call(account) + def call(account, **options) return if account.featured_collection_url.blank? || account.suspended? || account.local? @account = account + @options = options @json = fetch_resource(@account.featured_collection_url, true, local_follower) return unless supported_context?(@json) @@ -36,7 +37,15 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService end def process_items(items) + process_note_items(items) if @options[:note] + process_hashtag_items(items) if @options[:hashtag] + end + + def process_note_items(items) status_ids = items.filter_map do |item| + type = item['type'] + next unless type == 'Note' + uri = value_or_id(item) next if ActivityPub::TagManager.instance.local_uri?(uri) @@ -67,6 +76,26 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService end end + def process_hashtag_items(items) + names = items.filter_map { |item| item['type'] == 'Hashtag' && item['name']&.delete_prefix('#') }.map { |name| HashtagNormalizer.new.normalize(name) } + to_remove = [] + to_add = names + + FeaturedTag.where(account: @account).map(&:name).each do |name| + if names.include?(name) + to_add.delete(name) + else + to_remove << name + end + end + + FeaturedTag.includes(:tag).where(account: @account, tags: { name: to_remove }).delete_all unless to_remove.empty? + + to_add.each do |name| + FeaturedTag.create!(account: @account, name: name) + end + end + def local_follower return @local_follower if defined?(@local_follower) diff --git a/app/services/activitypub/fetch_featured_tags_collection_service.rb b/app/services/activitypub/fetch_featured_tags_collection_service.rb new file mode 100644 index 000000000..555919938 --- /dev/null +++ b/app/services/activitypub/fetch_featured_tags_collection_service.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +class ActivityPub::FetchFeaturedTagsCollectionService < BaseService + include JsonLdHelper + + def call(account, url) + return if url.blank? || account.suspended? || account.local? + + @account = account + @json = fetch_resource(url, true, local_follower) + + return unless supported_context?(@json) + + process_items(collection_items(@json)) + end + + private + + def collection_items(collection) + all_items = [] + + collection = fetch_collection(collection['first']) if collection['first'].present? + + while collection.is_a?(Hash) + items = begin + case collection['type'] + when 'Collection', 'CollectionPage' + collection['items'] + when 'OrderedCollection', 'OrderedCollectionPage' + collection['orderedItems'] + end + end + + break if items.blank? + + all_items.concat(items) + + break if all_items.size >= FeaturedTag::LIMIT + + collection = collection['next'].present? ? fetch_collection(collection['next']) : nil + end + + all_items + end + + def fetch_collection(collection_or_uri) + return collection_or_uri if collection_or_uri.is_a?(Hash) + return if invalid_origin?(collection_or_uri) + + fetch_resource_without_id_validation(collection_or_uri, local_follower, true) + end + + def process_items(items) + names = items.filter_map { |item| item['type'] == 'Hashtag' && item['name']&.delete_prefix('#') }.map { |name| HashtagNormalizer.new.normalize(name) } + to_remove = [] + to_add = names + + FeaturedTag.where(account: @account).map(&:name).each do |name| + if names.include?(name) + to_add.delete(name) + else + to_remove << name + end + end + + FeaturedTag.includes(:tag).where(account: @account, tags: { name: to_remove }).delete_all unless to_remove.empty? + + to_add.each do |name| + FeaturedTag.create!(account: @account, name: name) + end + end + + def local_follower + return @local_follower if defined?(@local_follower) + + @local_follower = @account.followers.local.without_suspended.first + end +end diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb index 456b3524b..3834d79cc 100644 --- a/app/services/activitypub/process_account_service.rb +++ b/app/services/activitypub/process_account_service.rb @@ -39,6 +39,7 @@ class ActivityPub::ProcessAccountService < BaseService unless @options[:only_key] || @account.suspended? check_featured_collection! if @account.featured_collection_url.present? + check_featured_tags_collection! if @json['featuredTags'].present? check_links! unless @account.fields.empty? end @@ -149,7 +150,11 @@ class ActivityPub::ProcessAccountService < BaseService end def check_featured_collection! - ActivityPub::SynchronizeFeaturedCollectionWorker.perform_async(@account.id) + ActivityPub::SynchronizeFeaturedCollectionWorker.perform_async(@account.id, { 'hashtag' => @json['featuredTags'].blank? }) + end + + def check_featured_tags_collection! + ActivityPub::SynchronizeFeaturedTagsCollectionWorker.perform_async(@account.id, @json['featuredTags']) end def check_links! diff --git a/app/workers/activitypub/synchronize_featured_collection_worker.rb b/app/workers/activitypub/synchronize_featured_collection_worker.rb index 7a0898e89..f67d693cb 100644 --- a/app/workers/activitypub/synchronize_featured_collection_worker.rb +++ b/app/workers/activitypub/synchronize_featured_collection_worker.rb @@ -5,8 +5,10 @@ class ActivityPub::SynchronizeFeaturedCollectionWorker sidekiq_options queue: 'pull', lock: :until_executed - def perform(account_id) - ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id)) + def perform(account_id, options = {}) + options = { note: true, hashtag: false }.deep_merge(options.deep_symbolize_keys) + + ActivityPub::FetchFeaturedCollectionService.new.call(Account.find(account_id), **options) rescue ActiveRecord::RecordNotFound true end diff --git a/app/workers/activitypub/synchronize_featured_tags_collection_worker.rb b/app/workers/activitypub/synchronize_featured_tags_collection_worker.rb new file mode 100644 index 000000000..14af4f725 --- /dev/null +++ b/app/workers/activitypub/synchronize_featured_tags_collection_worker.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class ActivityPub::SynchronizeFeaturedTagsCollectionWorker + include Sidekiq::Worker + + sidekiq_options queue: 'pull', lock: :until_executed + + def perform(account_id, url) + ActivityPub::FetchFeaturedTagsCollectionService.new.call(Account.find(account_id), url) + rescue ActiveRecord::RecordNotFound + true + end +end diff --git a/app/workers/activitypub/update_distribution_worker.rb b/app/workers/activitypub/update_distribution_worker.rb index 81fde63b8..d0391bb6f 100644 --- a/app/workers/activitypub/update_distribution_worker.rb +++ b/app/workers/activitypub/update_distribution_worker.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ActivityPub::UpdateDistributionWorker < ActivityPub::RawDistributionWorker + sidekiq_options queue: 'push', lock: :until_executed + # Distribute an profile update to servers that might have a copy # of the account in question def perform(account_id, options = {}) -- cgit From 94feb2b93ff1f381f8dba1ad361a2afd134f02a8 Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Fri, 21 Oct 2022 18:48:22 +0900 Subject: Fix `FetchFeaturedCollectionService` spec (#19401) Regression from #19380 --- app/services/activitypub/fetch_featured_collection_service.rb | 3 +-- spec/services/activitypub/fetch_featured_collection_service_spec.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) (limited to 'app/services') diff --git a/app/services/activitypub/fetch_featured_collection_service.rb b/app/services/activitypub/fetch_featured_collection_service.rb index 026fe24c5..50a187ad9 100644 --- a/app/services/activitypub/fetch_featured_collection_service.rb +++ b/app/services/activitypub/fetch_featured_collection_service.rb @@ -43,8 +43,7 @@ class ActivityPub::FetchFeaturedCollectionService < BaseService def process_note_items(items) status_ids = items.filter_map do |item| - type = item['type'] - next unless type == 'Note' + next unless item.is_a?(String) || item['type'] == 'Note' uri = value_or_id(item) next if ActivityPub::TagManager.instance.local_uri?(uri) diff --git a/spec/services/activitypub/fetch_featured_collection_service_spec.rb b/spec/services/activitypub/fetch_featured_collection_service_spec.rb index f552b9dc0..e6336dc1b 100644 --- a/spec/services/activitypub/fetch_featured_collection_service_spec.rb +++ b/spec/services/activitypub/fetch_featured_collection_service_spec.rb @@ -65,7 +65,7 @@ RSpec.describe ActivityPub::FetchFeaturedCollectionService, type: :service do stub_request(:get, 'https://example.com/account/pinned/3').to_return(status: 404) stub_request(:get, 'https://example.com/account/pinned/4').to_return(status: 200, body: Oj.dump(status_json_4)) - subject.call(actor) + subject.call(actor, note: true, hashtag: false) end it 'sets expected posts as pinned posts' do -- cgit From 74ead7d10698c7f18ca22c07d2f04ff81f419097 Mon Sep 17 00:00:00 2001 From: Takeshi Umeda Date: Sun, 23 Oct 2022 01:30:55 +0900 Subject: 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 --- app/controllers/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 ++++++++++++++++++++-- app/serializers/activitypub/hashtag_serializer.rb | 2 ++ app/serializers/activitypub/remove_serializer.rb | 23 ++++++++++++++++++++-- app/services/create_featured_tag_service.rb | 21 ++++++++++++++++++++ app/services/remove_featured_tag_service.rb | 18 +++++++++++++++++ .../activitypub/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 (limited to 'app/services') diff --git a/app/controllers/api/v1/featured_tags_controller.rb b/app/controllers/api/v1/featured_tags_controller.rb index a67db7040..edb42a94e 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 ae714e912..cc6ec0843 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 ec234a6fd..3f5cddce6 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 6f5aab17f..436b05086 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 90929c57f..2b24eb8cc 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 7fefda59d..fb224f8a9 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 000000000..c99d16113 --- /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 000000000..2aa70e8fc --- /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 000000000..a84c7d214 --- /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 000000000..065ec79d8 --- /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 -- cgit From 45d3b324883c1e72ad5f9820d3c23f7f779f28db Mon Sep 17 00:00:00 2001 From: Yamagishi Kazutoshi Date: Sun, 23 Oct 2022 06:14:58 +0900 Subject: Fix `Settings::FeaturedTagsController` (#19418) Regression from #19409 --- app/controllers/settings/featured_tags_controller.rb | 9 +++------ app/services/create_featured_tag_service.rb | 10 +++++++--- 2 files changed, 10 insertions(+), 9 deletions(-) (limited to 'app/services') diff --git a/app/controllers/settings/featured_tags_controller.rb b/app/controllers/settings/featured_tags_controller.rb index cc6ec0843..b3db04a42 100644 --- a/app/controllers/settings/featured_tags_controller.rb +++ b/app/controllers/settings/featured_tags_controller.rb @@ -10,8 +10,9 @@ class Settings::FeaturedTagsController < Settings::BaseController end def create - if !featured_tag_exists? - CreateFeaturedTagService.new.call(current_account, featured_tag_params[:name]) + @featured_tag = CreateFeaturedTagService.new.call(current_account, featured_tag_params[:name], force: false) + + if @featured_tag.valid? redirect_to settings_featured_tags_path else set_featured_tags @@ -28,10 +29,6 @@ class Settings::FeaturedTagsController < Settings::BaseController 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/services/create_featured_tag_service.rb b/app/services/create_featured_tag_service.rb index c99d16113..3cc59156d 100644 --- a/app/services/create_featured_tag_service.rb +++ b/app/services/create_featured_tag_service.rb @@ -3,14 +3,18 @@ class CreateFeaturedTagService < BaseService include Payloadable - def call(account, name) + def call(account, name, force: true) @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) + rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid => e + if force && e.is_a(ActiveRecord::RecordNotUnique) + FeaturedTag.by_name(name).find_by!(account: account) + else + account.featured_tags.new(name: name) + end end private -- cgit From 1ae508bf2faae016b88d15e273b0dc01de4fd7af Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 26 Oct 2022 12:10:02 +0200 Subject: Change unauthenticated search to not support pagination in REST API (#19326) - Only exact search matches for queries with < 5 characters - Do not support queries with `offset` (pagination) - Return HTTP 401 on truthy `resolve` instead of overriding to false --- app/controllers/api/v2/search_controller.rb | 13 ++++- app/services/account_search_service.rb | 5 ++ spec/controllers/api/v2/search_controller_spec.rb | 62 ++++++++++++++++++++--- 3 files changed, 71 insertions(+), 9 deletions(-) (limited to 'app/services') diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb index e384ecbaf..4d20aeb10 100644 --- a/app/controllers/api/v2/search_controller.rb +++ b/app/controllers/api/v2/search_controller.rb @@ -6,6 +6,7 @@ class Api::V2::SearchController < Api::BaseController RESULTS_LIMIT = 20 before_action -> { authorize_if_got_token! :read, :'read:search' } + before_action :validate_search_params! def index @search = Search.new(search_results) @@ -18,12 +19,22 @@ class Api::V2::SearchController < Api::BaseController private + def validate_search_params! + params.require(:q) + + return if user_signed_in? + + return render json: { error: 'Search queries pagination is not supported without authentication' }, status: 401 if params[:offset].present? + + render json: { error: 'Search queries that resolve remote resources are not supported without authentication' }, status: 401 if truthy_param?(:resolve) + end + def search_results SearchService.new.call( params[:q], current_account, limit_param(RESULTS_LIMIT), - search_params.merge(resolve: user_signed_in? ? truthy_param?(:resolve) : false, exclude_unreviewed: truthy_param?(:exclude_unreviewed)) + search_params.merge(resolve: truthy_param?(:resolve), exclude_unreviewed: truthy_param?(:exclude_unreviewed)) ) end diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index 4dcae20eb..35b2e05f5 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -3,6 +3,9 @@ class AccountSearchService < BaseService attr_reader :query, :limit, :offset, :options, :account + # Min. number of characters to look for non-exact matches + MIN_QUERY_LENGTH = 5 + def call(query, account = nil, options = {}) @acct_hint = query&.start_with?('@') @query = query&.strip&.gsub(/\A@/, '') @@ -135,6 +138,8 @@ class AccountSearchService < BaseService end def limit_for_non_exact_results + return 0 if @account.nil? && query.size < MIN_QUERY_LENGTH + if exact_match? limit - 1 else diff --git a/spec/controllers/api/v2/search_controller_spec.rb b/spec/controllers/api/v2/search_controller_spec.rb index fa20e1e51..d417ea58c 100644 --- a/spec/controllers/api/v2/search_controller_spec.rb +++ b/spec/controllers/api/v2/search_controller_spec.rb @@ -5,18 +5,64 @@ require 'rails_helper' RSpec.describe Api::V2::SearchController, type: :controller do render_views - let(:user) { Fabricate(:user) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:search') } + context 'with token' do + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:search') } - before do - allow(controller).to receive(:doorkeeper_token) { token } + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + describe 'GET #index' do + before do + get :index, params: { q: 'test' } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end end - describe 'GET #index' do - it 'returns http success' do - get :index, params: { q: 'test' } + context 'without token' do + describe 'GET #index' do + let(:search_params) {} + + before do + get :index, params: search_params + end + + context 'with a `q` shorter than 5 characters' do + let(:search_params) { { q: 'test' } } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + context 'with a `q` equal to or longer than 5 characters' do + let(:search_params) { { q: 'test1' } } + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + context 'with truthy `resolve`' do + let(:search_params) { { q: 'test1', resolve: '1' } } + + it 'returns http unauthorized' do + expect(response).to have_http_status(401) + end + end + + context 'with `offset`' do + let(:search_params) { { q: 'test1', offset: 1 } } - expect(response).to have_http_status(200) + it 'returns http unauthorized' do + expect(response).to have_http_status(401) + end + end + end end end end -- cgit From 7d25f72b9fb5f2940b998579da00a11f8a65fb40 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 26 Oct 2022 13:00:43 +0200 Subject: Fix negatives values in search index causing queries to fail (#19464) --- app/services/account_search_service.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'app/services') diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index 35b2e05f5..de1dc7b8e 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -105,7 +105,7 @@ class AccountSearchService < BaseService { script_score: { script: { - source: "(doc['followers_count'].value + 0.0) / (doc['followers_count'].value + doc['following_count'].value + 1)", + source: "(Math.max(doc['followers_count'].value, 0) + 0.0) / (Math.max(doc['followers_count'].value, 0) + Math.max(doc['following_count'].value, 0) + 1)", }, }, } @@ -113,10 +113,10 @@ class AccountSearchService < BaseService def followers_score_function { - field_value_factor: { - field: 'followers_count', - modifier: 'log2p', - missing: 0, + script_score: { + script: { + source: "log2p(Math.max(doc['followers_count'].value, 0))", + }, }, } end -- cgit From f6bcf86caf6a88b020c435bfab4c1ba8d70dd6db Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 27 Oct 2022 02:10:38 +0200 Subject: Fix wrong math function used in search query (#19481) --- app/services/account_search_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/services') diff --git a/app/services/account_search_service.rb b/app/services/account_search_service.rb index de1dc7b8e..9f2330a94 100644 --- a/app/services/account_search_service.rb +++ b/app/services/account_search_service.rb @@ -115,7 +115,7 @@ class AccountSearchService < BaseService { script_score: { script: { - source: "log2p(Math.max(doc['followers_count'].value, 0))", + source: "Math.log10(Math.max(doc['followers_count'].value, 0) + 2)", }, }, } -- cgit