From 3cc4b59b41bf296cf4e92190f6b66eecfb2b82ce Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Fri, 12 Dec 2025 14:09:55 +0100 Subject: [PATCH] First draft of API to add items to a collection (#37222) --- .../v1_alpha/collection_items_controller.rb | 41 ++++++++++++++ .../rest/collection_item_serializer.rb | 6 +- config/routes/api.rb | 4 +- .../api/v1_alpha/collection_items_spec.rb | 55 +++++++++++++++++++ .../rest/collection_item_serializer_spec.rb | 2 + 5 files changed, 106 insertions(+), 2 deletions(-) create mode 100644 app/controllers/api/v1_alpha/collection_items_controller.rb create mode 100644 spec/requests/api/v1_alpha/collection_items_spec.rb diff --git a/app/controllers/api/v1_alpha/collection_items_controller.rb b/app/controllers/api/v1_alpha/collection_items_controller.rb new file mode 100644 index 00000000000..cc2e5cdef12 --- /dev/null +++ b/app/controllers/api/v1_alpha/collection_items_controller.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class Api::V1Alpha::CollectionItemsController < Api::BaseController + include Authorization + + before_action :check_feature_enabled + + before_action -> { doorkeeper_authorize! :write, :'write:collections' } + + before_action :require_user! + + before_action :set_collection + before_action :set_account, only: [:create] + + after_action :verify_authorized + + def create + authorize @collection, :update? + authorize @account, :feature? + + @item = AddAccountToCollectionService.new.call(@collection, @account) + + render json: @item, serializer: REST::CollectionItemSerializer + end + + private + + def set_collection + @collection = Collection.find(params[:collection_id]) + end + + def set_account + return render(json: { error: '`account_id` parameter is missing' }, status: 422) if params[:account_id].blank? + + @account = Account.find(params[:account_id]) + end + + def check_feature_enabled + raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled? + end +end diff --git a/app/serializers/rest/collection_item_serializer.rb b/app/serializers/rest/collection_item_serializer.rb index c0acc87bfd4..d35a8fdef28 100644 --- a/app/serializers/rest/collection_item_serializer.rb +++ b/app/serializers/rest/collection_item_serializer.rb @@ -3,7 +3,11 @@ class REST::CollectionItemSerializer < ActiveModel::Serializer delegate :accepted?, to: :object - attributes :position, :state + attributes :id, :position, :state belongs_to :account, serializer: REST::AccountSerializer, if: :accepted? + + def id + object.id.to_s + end end diff --git a/config/routes/api.rb b/config/routes/api.rb index ad58e8744fa..563191614c6 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -12,7 +12,9 @@ namespace :api, format: false do resources :async_refreshes, only: :show - resources :collections, only: [:show, :create, :update, :destroy] + resources :collections, only: [:show, :create, :update, :destroy] do + resources :items, only: [:create], controller: 'collection_items' + end end # JSON / REST API diff --git a/spec/requests/api/v1_alpha/collection_items_spec.rb b/spec/requests/api/v1_alpha/collection_items_spec.rb new file mode 100644 index 00000000000..880fd5d47d1 --- /dev/null +++ b/spec/requests/api/v1_alpha/collection_items_spec.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Api::V1Alpha::CollectionItems', feature: :collections do + include_context 'with API authentication', oauth_scopes: 'read:collections write:collections' + + describe 'POST /api/v1_alpha/collections/:collection_id/items' do + subject do + post "/api/v1_alpha/collections/#{collection.id}/items", headers: headers, params: params + end + + let(:collection) { Fabricate(:collection, account: user.account) } + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read' + + context 'when user is owner of the collection' do + context 'with valid params' do + let(:other_account) { Fabricate(:account) } + let(:params) { { account_id: other_account.id } } + + it 'creates a collection item and returns http success' do + expect do + subject + end.to change(collection.collection_items, :count).by(1) + + expect(response).to have_http_status(200) + end + end + + context 'with invalid params' do + it 'returns http unprocessable content' do + expect do + subject + end.to_not change(CollectionItem, :count) + + expect(response).to have_http_status(422) + end + end + end + + context 'when user is not the owner of the collection' do + let(:collection) { Fabricate(:collection) } + let(:other_account) { Fabricate(:account) } + let(:params) { { account_id: other_account.id } } + + it 'returns http forbidden' do + subject + + expect(response).to have_http_status(403) + end + end + end +end diff --git a/spec/serializers/rest/collection_item_serializer_spec.rb b/spec/serializers/rest/collection_item_serializer_spec.rb index bcb7458c4de..b12553ec034 100644 --- a/spec/serializers/rest/collection_item_serializer_spec.rb +++ b/spec/serializers/rest/collection_item_serializer_spec.rb @@ -7,6 +7,7 @@ RSpec.describe REST::CollectionItemSerializer do let(:collection_item) do Fabricate(:collection_item, + id: 2342, state:, position: 4) end @@ -17,6 +18,7 @@ RSpec.describe REST::CollectionItemSerializer do it 'includes the relevant attributes including the account' do expect(subject) .to include( + 'id' => '2342', 'account' => an_instance_of(Hash), 'state' => 'accepted', 'position' => 4