diff --git a/app/controllers/api/v1_alpha/collections_controller.rb b/app/controllers/api/v1_alpha/collections_controller.rb new file mode 100644 index 00000000000..5583bb395da --- /dev/null +++ b/app/controllers/api/v1_alpha/collections_controller.rb @@ -0,0 +1,29 @@ +# frozen_string_literal: true + +class Api::V1Alpha::CollectionsController < Api::BaseController + rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e| + render json: { error: ValidationErrorFormatter.new(e).as_json }, status: 422 + end + + before_action :check_feature_enabled + + before_action -> { doorkeeper_authorize! :write, :'write:collections' }, only: [:create] + + before_action :require_user! + + def create + @collection = CreateCollectionService.new.call(collection_params, current_user.account) + + render json: @collection, serializer: REST::CollectionSerializer + end + + private + + def collection_params + params.permit(:name, :description, :sensitive, :discoverable, :tag, account_ids: []) + end + + def check_feature_enabled + raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled? + end +end diff --git a/app/serializers/rest/collection_serializer.rb b/app/serializers/rest/collection_serializer.rb new file mode 100644 index 00000000000..c03cc538566 --- /dev/null +++ b/app/serializers/rest/collection_serializer.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +class REST::CollectionSerializer < ActiveModel::Serializer + attributes :uri, :name, :description, :local, :sensitive, :discoverable, + :created_at, :updated_at + + belongs_to :account, serializer: REST::AccountSerializer +end diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb index 516db258dfa..908acb55033 100644 --- a/config/initializers/doorkeeper.rb +++ b/config/initializers/doorkeeper.rb @@ -75,6 +75,7 @@ Doorkeeper.configure do :'write:accounts', :'write:blocks', :'write:bookmarks', + :'write:collections', :'write:conversations', :'write:favourites', :'write:filters', @@ -89,6 +90,7 @@ Doorkeeper.configure do :'read:accounts', :'read:blocks', :'read:bookmarks', + :'read:collections', :'read:favourites', :'read:filters', :'read:follows', diff --git a/config/routes/api.rb b/config/routes/api.rb index 34b2e255da6..2fa3d4d8335 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -7,6 +7,8 @@ namespace :api, format: false do # Experimental JSON / REST API namespace :v1_alpha do resources :async_refreshes, only: :show + + resources :collections, only: [:create] end # JSON / REST API diff --git a/spec/requests/api/v1_alpha/collections_spec.rb b/spec/requests/api/v1_alpha/collections_spec.rb new file mode 100644 index 00000000000..5f9c5e5f347 --- /dev/null +++ b/spec/requests/api/v1_alpha/collections_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do + include_context 'with API authentication', oauth_scopes: 'read:collections write:collections' + + describe 'POST /api/v1_alpha/collections' do + subject do + post '/api/v1_alpha/collections', headers: headers, params: params + end + + let(:params) { {} } + + it_behaves_like 'forbidden for wrong scope', 'read' + + context 'with valid params' do + let(:params) do + { + name: 'Low-traffic bots', + description: 'Really nice bots, please follow', + sensitive: '0', + discoverable: '1', + } + end + + it 'creates a collection and returns http success' do + expect do + subject + end.to change(Collection, :count).by(1) + + expect(response).to have_http_status(200) + end + end + + context 'with invalid params' do + it 'returns http unprocessable content and detailed errors' do + expect do + subject + end.to_not change(Collection, :count) + + expect(response).to have_http_status(422) + expect(response.parsed_body).to include({ + 'error' => a_hash_including({ + 'details' => a_hash_including({ + 'name' => [{ 'error' => 'ERR_BLANK', 'description' => "can't be blank" }], + 'description' => [{ 'error' => 'ERR_BLANK', 'description' => "can't be blank" }], + }), + }), + }) + end + end + end +end diff --git a/spec/serializers/rest/collection_serializer_spec.rb b/spec/serializers/rest/collection_serializer_spec.rb new file mode 100644 index 00000000000..c498937b508 --- /dev/null +++ b/spec/serializers/rest/collection_serializer_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe REST::CollectionSerializer do + subject { serialized_record_json(collection, described_class) } + + let(:collection) do + Fabricate(:collection, + name: 'Exquisite follows', + description: 'Always worth a follow', + local: true, + sensitive: true, + discoverable: false) + end + + it 'includes the relevant attributes' do + expect(subject) + .to include( + 'account' => an_instance_of(Hash), + 'name' => 'Exquisite follows', + 'description' => 'Always worth a follow', + 'local' => true, + 'sensitive' => true, + 'discoverable' => false, + 'created_at' => match_api_datetime_format, + 'updated_at' => match_api_datetime_format + ) + end +end