Update GET /followers API endpoint (#13287)
* Add created_at to API followers controller * Add sorting * Update API docs * Update app/controllers/api/v0/followers_controller.rb Co-authored-by: Jamie Gaskins <jgaskins@gmail.com> * Update app/controllers/api/v0/followers_controller.rb Co-authored-by: Jamie Gaskins <jgaskins@gmail.com> * Use correct date format * Update API docs * Add JsonApiSortParams concern * Update controller to use concern * Rename and add docs * Update API docs * Remove leftover file * Undo gem changes * Consistently use field vs param * Reset vendor/cache for git dependencies Co-authored-by: Jamie Gaskins <jgaskins@gmail.com>
This commit is contained in:
parent
a5e572db21
commit
d1c3380161
6 changed files with 122 additions and 8 deletions
|
|
@ -1,11 +1,13 @@
|
|||
module Api
|
||||
module V0
|
||||
class FollowersController < ApiController
|
||||
include JsonApiSortParam
|
||||
|
||||
before_action :authenticate_with_api_key_or_current_user!
|
||||
before_action -> { limit_per_page(default: 80, max: 1000) }
|
||||
|
||||
USERS_ATTRIBUTES_FOR_SERIALIZATION = %i[
|
||||
id follower_id follower_type
|
||||
id follower_id follower_type created_at
|
||||
].freeze
|
||||
private_constant :USERS_ATTRIBUTES_FOR_SERIALIZATION
|
||||
|
||||
|
|
@ -13,7 +15,7 @@ module Api
|
|||
@follows = Follow.followable_user(@user.id)
|
||||
.includes(:follower)
|
||||
.select(USERS_ATTRIBUTES_FOR_SERIALIZATION)
|
||||
.order(created_at: :desc)
|
||||
.order(order_criteria)
|
||||
.page(params[:page])
|
||||
.per(@follows_limit)
|
||||
end
|
||||
|
|
@ -24,6 +26,14 @@ module Api
|
|||
per_page = (params[:per_page] || default).to_i
|
||||
@follows_limit = [per_page, max].min
|
||||
end
|
||||
|
||||
def order_criteria
|
||||
parse_sort_param(
|
||||
params[:sort],
|
||||
allowed_fields: [:created_at],
|
||||
default_sort: { created_at: :desc },
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
32
app/controllers/concerns/json_api_sort_param.rb
Normal file
32
app/controllers/concerns/json_api_sort_param.rb
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
module JsonApiSortParam
|
||||
# Handles JSON API style sort params
|
||||
#
|
||||
# @param param_string [String] The unmodified request param
|
||||
# @param allowed_fields [Array<Symbol>] Fields that can be sorted by. This
|
||||
# array determines the sort order of the resulting hash.
|
||||
# @param default_sort [{Symbol => Symbol}] The default sort order. Used when
|
||||
# the param string is nil/empty or when parsing it results in an empty hash.
|
||||
def parse_sort_param(param_string, allowed_fields:, default_sort:)
|
||||
fields = param_string.to_s.split(",")
|
||||
unfiltered_hash = fields_to_hash(fields)
|
||||
sort = sort_and_filter(unfiltered_hash, allowed_fields)
|
||||
sort.presence || default_sort
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fields_to_hash(fields)
|
||||
fields.each_with_object({}) do |field, result|
|
||||
if field.start_with?("-")
|
||||
result[field[1..]] = :desc
|
||||
else
|
||||
result[field] = :asc
|
||||
end
|
||||
end.symbolize_keys
|
||||
end
|
||||
|
||||
def sort_and_filter(fields_hash, allowed_fields)
|
||||
field_order = allowed_fields.each_with_index.to_h
|
||||
fields_hash.slice(*allowed_fields).sort_by { |k, _v| field_order[k] }.to_h
|
||||
end
|
||||
end
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
json.array! @follows do |follow|
|
||||
json.type_of "user_follower"
|
||||
json.id follow.id
|
||||
json.type_of "user_follower"
|
||||
json.id follow.id
|
||||
json.created_at utc_iso_timestamp(follow.created_at)
|
||||
|
||||
json.partial! "api/v0/shared/follows", user: follow.follower
|
||||
end
|
||||
|
|
|
|||
|
|
@ -687,6 +687,7 @@ components:
|
|||
type: object
|
||||
required:
|
||||
- type_of
|
||||
- created_at
|
||||
- id
|
||||
- name
|
||||
- path
|
||||
|
|
@ -695,6 +696,10 @@ components:
|
|||
properties:
|
||||
type_of:
|
||||
type: string
|
||||
created_at:
|
||||
description: Date the user became a follower
|
||||
type: string
|
||||
format: date-time
|
||||
id:
|
||||
description: Follow id
|
||||
type: integer
|
||||
|
|
@ -1552,12 +1557,14 @@ components:
|
|||
value:
|
||||
- type_of: user_follower
|
||||
id: 12
|
||||
created_at: "2021-04-02T04:21:46Z"
|
||||
name: Mrs. Neda Morissette
|
||||
path: "/nedamrsmorissette"
|
||||
username: nedamrsmorissette
|
||||
profile_image: https://res.cloudinary.com/...
|
||||
- type_of: user_follower
|
||||
id: 11
|
||||
created_at: "2021-04-02T04:21:46Z"
|
||||
name: Yoko Hintz
|
||||
path: "/yokohintz"
|
||||
username: yokohintz
|
||||
|
|
@ -1567,6 +1574,7 @@ components:
|
|||
value:
|
||||
- type_of: listing
|
||||
id: 1157
|
||||
created_at: "2021-04-07 08:29:42 UTC"
|
||||
title: TestBash Detroit
|
||||
slug: testbash-detroit-50gb
|
||||
body_markdown: "Do you want to learn about automation? Maybe you're interested in
|
||||
|
|
@ -2166,7 +2174,7 @@ paths:
|
|||
- $ref: '#/components/parameters/perPageParam30to1000'
|
||||
responses:
|
||||
"200":
|
||||
description: A list of articles sorted by descending publish date
|
||||
description: A list of articles sorted by descending publish date
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
|
|
@ -2709,6 +2717,17 @@ paths:
|
|||
parameters:
|
||||
- $ref: '#/components/parameters/pageParam'
|
||||
- $ref: '#/components/parameters/perPageParam80to1000'
|
||||
- in: query
|
||||
name: sort
|
||||
schema:
|
||||
type: string
|
||||
default: 'created_at'
|
||||
pattern: '^-?\w+(,-?\w+)*$'
|
||||
required: false
|
||||
description: |
|
||||
Specifies the sort order for the `created_at` param of the follow
|
||||
relationship. To sort by newest followers first (descending order)
|
||||
specify `?sort=-created_at`.
|
||||
responses:
|
||||
"200":
|
||||
description: A list of followers
|
||||
|
|
|
|||
42
spec/controllers/concerns/json_api_sort_param_spec.rb
Normal file
42
spec/controllers/concerns/json_api_sort_param_spec.rb
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe JsonApiSortParam do
|
||||
let!(:controller) { Class.new { include JsonApiSortParam }.new }
|
||||
|
||||
it "returns a default sort order if there are no sorting params" do
|
||||
params = controller.parse_sort_param(
|
||||
"",
|
||||
allowed_fields: [:created_at],
|
||||
default_sort: { created_at: :desc },
|
||||
)
|
||||
expect(params).to eq({ created_at: :desc })
|
||||
end
|
||||
|
||||
it "filters out non-allowed params", :aggregate_failures do
|
||||
params = controller.parse_sort_param(
|
||||
"-created_at,updated_at",
|
||||
allowed_fields: [:updated_at],
|
||||
default_sort: { created_at: :desc },
|
||||
)
|
||||
expect(params).not_to have_key(:created_at)
|
||||
expect(params).to have_key(:updated_at)
|
||||
end
|
||||
|
||||
it "generates a sort params hash" do
|
||||
params = controller.parse_sort_param(
|
||||
"created_at,-updated_at",
|
||||
allowed_fields: %i[created_at updated_at],
|
||||
default_sort: { created_at: :desc },
|
||||
)
|
||||
expect(params).to eq({ created_at: :asc, updated_at: :desc })
|
||||
end
|
||||
|
||||
it "sorts the params in the correct order" do
|
||||
params = controller.parse_sort_param(
|
||||
"-updated_at,created_at",
|
||||
allowed_fields: %i[created_at updated_at],
|
||||
default_sort: { created_at: :desc },
|
||||
)
|
||||
expect(params.to_a).to eq([%i[created_at asc], %i[updated_at desc]])
|
||||
end
|
||||
end
|
||||
|
|
@ -43,6 +43,7 @@ RSpec.describe "Api::V0::FollowersController", type: :request do
|
|||
expect(response_follower["path"]).to eq(follower.path)
|
||||
expect(response_follower["username"]).to eq(follower.username)
|
||||
expect(response_follower["profile_image"]).to eq(Images::Profile.call(follower.profile_image_url, length: 60))
|
||||
expect(response_follower["created_at"]).to be_an_instance_of(String)
|
||||
end
|
||||
|
||||
it "supports pagination" do
|
||||
|
|
@ -58,12 +59,21 @@ RSpec.describe "Api::V0::FollowersController", type: :request do
|
|||
expect(response.parsed_body.length).to eq(0)
|
||||
end
|
||||
|
||||
it "order results for reverse following date" do
|
||||
it "orders results by descending following date by default" do
|
||||
follower2.follow(user)
|
||||
|
||||
follows = user.followings.order(id: :desc).last(2).map(&:id)
|
||||
|
||||
get api_followers_users_path, headers: headers
|
||||
|
||||
follows = user.followings.order(id: :desc).last(2).map(&:id)
|
||||
result = response.parsed_body.map { |f| f["id"] }
|
||||
expect(result).to eq(follows)
|
||||
end
|
||||
|
||||
it "orders results by ascending following date if the 'sort' param is specified" do
|
||||
follower2.follow(user)
|
||||
|
||||
follows = user.followings.order(id: :asc).last(2).map(&:id)
|
||||
get api_followers_users_path, headers: headers, params: { sort: "created_at" }
|
||||
result = response.parsed_body.map { |f| f["id"] }
|
||||
expect(result).to eq(follows)
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue