mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-13 15:58:50 +00:00
Draft API to get all collections by an account (#37139)
This commit is contained in:
@@ -3,20 +3,34 @@
|
||||
class Api::V1Alpha::CollectionsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
DEFAULT_COLLECTIONS_LIMIT = 40
|
||||
|
||||
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 -> { authorize_if_got_token! :read, :'read:collections' }, only: [:index, :show]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:collections' }, only: [:create, :update, :destroy]
|
||||
|
||||
before_action :require_user!, only: [:create, :update, :destroy]
|
||||
|
||||
before_action :set_account, only: [:index]
|
||||
before_action :set_collections, only: [:index]
|
||||
before_action :set_collection, only: [:show, :update, :destroy]
|
||||
|
||||
after_action :insert_pagination_headers, only: [:index]
|
||||
|
||||
after_action :verify_authorized
|
||||
|
||||
def index
|
||||
cache_if_unauthenticated!
|
||||
authorize Collection, :index?
|
||||
|
||||
render json: @collections, each_serializer: REST::BaseCollectionSerializer
|
||||
end
|
||||
|
||||
def show
|
||||
cache_if_unauthenticated!
|
||||
authorize @collection, :show?
|
||||
@@ -50,6 +64,18 @@ class Api::V1Alpha::CollectionsController < Api::BaseController
|
||||
|
||||
private
|
||||
|
||||
def set_account
|
||||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def set_collections
|
||||
@collections = @account.collections
|
||||
.with_item_count
|
||||
.order(created_at: :desc)
|
||||
.offset(offset_param)
|
||||
.limit(limit_param(DEFAULT_COLLECTIONS_LIMIT))
|
||||
end
|
||||
|
||||
def set_collection
|
||||
@collection = Collection.find(params[:id])
|
||||
end
|
||||
@@ -65,4 +91,24 @@ class Api::V1Alpha::CollectionsController < Api::BaseController
|
||||
def check_feature_enabled
|
||||
raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled?
|
||||
end
|
||||
|
||||
def next_path
|
||||
return unless records_continue?
|
||||
|
||||
api_v1_alpha_account_collections_url(@account, pagination_params(offset: offset_param + limit_param(DEFAULT_COLLECTIONS_LIMIT)))
|
||||
end
|
||||
|
||||
def prev_path
|
||||
return if offset_param.zero?
|
||||
|
||||
api_v1_alpha_account_collections_url(@account, pagination_params(offset: offset_param - limit_param(DEFAULT_COLLECTIONS_LIMIT)))
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
((offset_param * limit_param(DEFAULT_COLLECTIONS_LIMIT)) + @collections.size) < @account.collections.size
|
||||
end
|
||||
|
||||
def offset_param
|
||||
params[:offset].to_i
|
||||
end
|
||||
end
|
||||
|
||||
@@ -39,6 +39,11 @@ class Collection < ApplicationRecord
|
||||
validate :items_do_not_exceed_limit
|
||||
|
||||
scope :with_items, -> { includes(:collection_items).merge(CollectionItem.with_accounts) }
|
||||
scope :with_item_count, lambda {
|
||||
select('collections.*, COUNT(collection_items.id)')
|
||||
.left_joins(:collection_items)
|
||||
.group(collections: :id)
|
||||
}
|
||||
|
||||
def remote?
|
||||
!local?
|
||||
|
||||
12
app/serializers/rest/base_collection_serializer.rb
Normal file
12
app/serializers/rest/base_collection_serializer.rb
Normal file
@@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::BaseCollectionSerializer < ActiveModel::Serializer
|
||||
attributes :uri, :name, :description, :local, :sensitive, :discoverable,
|
||||
:item_count, :created_at, :updated_at
|
||||
|
||||
belongs_to :tag, serializer: REST::StatusSerializer::TagSerializer
|
||||
|
||||
def item_count
|
||||
object.respond_to?(:item_count) ? object.item_count : object.collection_items.count
|
||||
end
|
||||
end
|
||||
@@ -1,11 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::CollectionSerializer < ActiveModel::Serializer
|
||||
attributes :uri, :name, :description, :local, :sensitive, :discoverable,
|
||||
:created_at, :updated_at
|
||||
|
||||
class REST::CollectionSerializer < REST::BaseCollectionSerializer
|
||||
belongs_to :account, serializer: REST::AccountSerializer
|
||||
belongs_to :tag, serializer: REST::StatusSerializer::TagSerializer
|
||||
|
||||
has_many :items, serializer: REST::CollectionItemSerializer
|
||||
|
||||
|
||||
@@ -6,6 +6,10 @@ namespace :api, format: false do
|
||||
|
||||
# Experimental JSON / REST API
|
||||
namespace :v1_alpha do
|
||||
resources :accounts, only: [] do
|
||||
resources :collections, only: [:index]
|
||||
end
|
||||
|
||||
resources :async_refreshes, only: :show
|
||||
|
||||
resources :collections, only: [:show, :create, :update, :destroy]
|
||||
|
||||
@@ -5,6 +5,58 @@ require 'rails_helper'
|
||||
RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do
|
||||
include_context 'with API authentication', oauth_scopes: 'read:collections write:collections'
|
||||
|
||||
describe 'GET /api/v1_alpha/accounts/:account_id/collections' do
|
||||
subject do
|
||||
get "/api/v1_alpha/accounts/#{account.id}/collections", headers: headers, params: params
|
||||
end
|
||||
|
||||
let(:params) { {} }
|
||||
|
||||
let(:account) { Fabricate(:account) }
|
||||
|
||||
before { Fabricate.times(3, :collection, account:) }
|
||||
|
||||
it 'returns all collections for the given account and http success' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.parsed_body.size).to eq 3
|
||||
end
|
||||
|
||||
context 'with limit param' do
|
||||
let(:params) { { limit: '1' } }
|
||||
|
||||
it 'returns only a single result' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.parsed_body.size).to eq 1
|
||||
|
||||
expect(response)
|
||||
.to include_pagination_headers(
|
||||
next: api_v1_alpha_account_collections_url(account, limit: 1, offset: 1)
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with limit and offset params' do
|
||||
let(:params) { { limit: '1', offset: '1' } }
|
||||
|
||||
it 'returns the correct result and headers' do
|
||||
subject
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.parsed_body.size).to eq 1
|
||||
|
||||
expect(response)
|
||||
.to include_pagination_headers(
|
||||
prev: api_v1_alpha_account_collections_url(account, limit: 1, offset: 0),
|
||||
next: api_v1_alpha_account_collections_url(account, limit: 1, offset: 2)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v1_alpha/collections/:id' do
|
||||
subject do
|
||||
get "/api/v1_alpha/collections/#{collection.id}", headers: headers
|
||||
|
||||
55
spec/serializers/rest/base_collection_serializer_spec.rb
Normal file
55
spec/serializers/rest/base_collection_serializer_spec.rb
Normal file
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe REST::BaseCollectionSerializer do
|
||||
subject do
|
||||
serialized_record_json(collection, described_class, options: {
|
||||
scope: current_user,
|
||||
scope_name: :current_user,
|
||||
})
|
||||
end
|
||||
|
||||
let(:current_user) { nil }
|
||||
|
||||
let(:tag) { Fabricate(:tag, name: 'discovery') }
|
||||
let(:collection) do
|
||||
Fabricate(:collection,
|
||||
name: 'Exquisite follows',
|
||||
description: 'Always worth a follow',
|
||||
local: true,
|
||||
sensitive: true,
|
||||
discoverable: false,
|
||||
tag:)
|
||||
end
|
||||
|
||||
it 'includes the relevant attributes' do
|
||||
expect(subject)
|
||||
.to include(
|
||||
'name' => 'Exquisite follows',
|
||||
'description' => 'Always worth a follow',
|
||||
'local' => true,
|
||||
'sensitive' => true,
|
||||
'discoverable' => false,
|
||||
'tag' => a_hash_including('name' => 'discovery'),
|
||||
'created_at' => match_api_datetime_format,
|
||||
'updated_at' => match_api_datetime_format
|
||||
)
|
||||
end
|
||||
|
||||
describe 'Counting items' do
|
||||
before do
|
||||
Fabricate.times(2, :collection_item, collection:)
|
||||
end
|
||||
|
||||
it 'can count items on demand' do
|
||||
expect(subject['item_count']).to eq 2
|
||||
end
|
||||
|
||||
it 'can use precalculated counts' do
|
||||
collection.define_singleton_method :item_count, -> { 8 }
|
||||
|
||||
expect(subject['item_count']).to eq 8
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user