Add service to add item to a collection (#37192)

This commit is contained in:
David Roetzel 2025-12-10 17:59:21 +01:00 committed by GitHub
parent d6f2a3ac8d
commit adf8a3601d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 162 additions and 0 deletions

View file

@ -455,6 +455,10 @@ class Account < ApplicationRecord
save!
end
def featureable?
local? && discoverable?
end
private
def prepare_contents

View file

@ -32,6 +32,8 @@ class CollectionItem < ApplicationRecord
validates :account, presence: true, if: :accepted?
validates :object_uri, presence: true, if: -> { account.nil? }
before_validation :set_position, on: :create
scope :ordered, -> { order(position: :asc) }
scope :with_accounts, -> { includes(account: [:account_stat, :user]) }
scope :not_blocked_by, ->(account) { where.not(accounts: { id: account.blocking }) }
@ -39,4 +41,12 @@ class CollectionItem < ApplicationRecord
def local_item_with_remote_account?
local? && account&.remote?
end
private
def set_position
return if position_changed?
self.position = self.class.where(collection_id:).maximum(:position).to_i + 1
end
end

View file

@ -64,4 +64,8 @@ class AccountPolicy < ApplicationPolicy
def review?
role.can?(:manage_taxonomies)
end
def feature?
record.featureable? && !current_account.blocking?(record) && !record.blocking?(current_account)
end
end

View file

@ -0,0 +1,23 @@
# frozen_string_literal: true
class AddAccountToCollectionService
def call(collection, account)
raise ArgumentError unless collection.local?
@collection = collection
@account = account
raise Mastodon::NotPermittedError, I18n.t('accounts.errors.cannot_be_added_to_collections') unless AccountPolicy.new(@collection.account, @account).feature?
create_collection_item
end
private
def create_collection_item
@collection.collection_items.create!(
account: @account,
state: :accepted
)
end
end

View file

@ -7,6 +7,8 @@ en:
hosted_on: Mastodon hosted on %{domain}
title: About
accounts:
errors:
cannot_be_added_to_collections: This account cannot be added to collections.
followers:
one: Follower
other: Followers

View file

@ -781,4 +781,37 @@ RSpec.describe Account do
expect(subject.reload.followers_count).to eq 15
end
end
describe '#featureable?' do
subject { Fabricate.build(:account, domain: (local ? nil : 'example.com'), discoverable:) }
context 'when account is local' do
let(:local) { true }
context 'when account is discoverable' do
let(:discoverable) { true }
it 'returns `true`' do
expect(subject.featureable?).to be true
end
end
context 'when account is not discoverable' do
let(:discoverable) { false }
it 'returns `false`' do
expect(subject.featureable?).to be false
end
end
end
context 'when account is remote' do
let(:local) { false }
let(:discoverable) { true }
it 'returns `false`' do
expect(subject.featureable?).to be false
end
end
end
end

View file

@ -38,4 +38,23 @@ RSpec.describe CollectionItem do
it { is_expected.to validate_presence_of(:object_uri) }
end
end
describe 'Creation' do
let(:collection) { Fabricate(:collection) }
let(:other_collection) { Fabricate(:collection) }
let(:account) { Fabricate(:account) }
let(:other_account) { Fabricate(:account) }
it 'automatically sets the `position` if absent' do
first_item = collection.collection_items.create(account:)
second_item = collection.collection_items.create(account: other_account)
unrelated_item = other_collection.collection_items.create(account:)
custom_item = other_collection.collection_items.create(account: other_account, position: 7)
expect(first_item.position).to eq 1
expect(second_item.position).to eq 2
expect(unrelated_item.position).to eq 1
expect(custom_item.position).to eq 7
end
end
end

View file

@ -156,4 +156,36 @@ RSpec.describe AccountPolicy do
end
end
end
permissions :feature? do
context 'when account is featureable?' do
it 'permits' do
expect(subject).to permit(alice, john)
end
end
context 'when account is not featureable' do
before { allow(alice).to receive(:featureable?).and_return(false) }
it 'denies' do
expect(subject).to_not permit(john, alice)
end
end
context 'when account is blocked' do
before { alice.block!(john) }
it 'denies' do
expect(subject).to_not permit(alice, john)
end
end
context 'when account is blocking' do
before { john.block!(alice) }
it 'denies' do
expect(subject).to_not permit(alice, john)
end
end
end
end

View file

@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe AddAccountToCollectionService do
subject { described_class.new }
let(:collection) { Fabricate.create(:collection) }
describe '#call' do
context 'when given a featurable account' do
let(:account) { Fabricate(:account) }
it 'creates a new CollectionItem in the `accepted` state' do
expect do
subject.call(collection, account)
end.to change(collection.collection_items, :count).by(1)
new_item = collection.collection_items.last
expect(new_item.state).to eq 'accepted'
expect(new_item.account).to eq account
end
end
context 'when given an account that is not featureable' do
let(:account) { Fabricate(:account, discoverable: false) }
it 'raises an error' do
expect do
subject.call(collection, account)
end.to raise_error(Mastodon::NotPermittedError)
end
end
end
end