API: Endpoint to get an organization's listings (#11823)
* add tests * add route * add controller action * add jbuilder file * update api docs * update query * update jbuilder listing * add organization/user and category to listing * update api docs/spec file
This commit is contained in:
parent
908ab99939
commit
6e2aab623e
8 changed files with 170 additions and 12 deletions
|
|
@ -1,7 +1,7 @@
|
|||
module Api
|
||||
module V0
|
||||
class OrganizationsController < ApiController
|
||||
before_action :find_organization, only: %i[users]
|
||||
before_action :find_organization, only: %i[users listings]
|
||||
|
||||
SHOW_ATTRIBUTES_FOR_SERIALIZATION = %i[
|
||||
username name summary twitter_username github_username url
|
||||
|
|
@ -15,6 +15,12 @@ module Api
|
|||
].freeze
|
||||
private_constant :USERS_FOR_SERIALIZATION
|
||||
|
||||
LISTINGS_FOR_SERIALIZATION = %i[
|
||||
id user_id organization_id title slug body_markdown cached_tag_list
|
||||
classified_listing_category_id processed_html published
|
||||
].freeze
|
||||
private_constant :LISTINGS_FOR_SERIALIZATION
|
||||
|
||||
def show
|
||||
@organization = Organization.select(SHOW_ATTRIBUTES_FOR_SERIALIZATION)
|
||||
.find_by!(username: params[:username])
|
||||
|
|
@ -28,6 +34,19 @@ module Api
|
|||
@users = @organization.users.select(USERS_FOR_SERIALIZATION).page(page).per(num)
|
||||
end
|
||||
|
||||
def listings
|
||||
per_page = (params[:per_page] || 30).to_i
|
||||
num = [per_page, 1000].min
|
||||
page = params[:page] || 1
|
||||
|
||||
@listings = @organization.listings.published
|
||||
.select(LISTINGS_FOR_SERIALIZATION).page(page).per(num)
|
||||
.includes(:user, :taggings, :listing_category)
|
||||
.order(bumped_at: :desc)
|
||||
|
||||
@listings = @listings.in_category(params[:category]) if params[:category].present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def find_organization
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
json.array! @listings do |listing|
|
||||
json.partial! "listing", listing: listing
|
||||
json.partial! "api/v0/shared/listing", listing: listing
|
||||
json.partial! "api/v0/shared/user", user: listing.user
|
||||
|
||||
if listing.organization
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
json.partial! "listing", listing: @listing
|
||||
json.partial! "api/v0/shared/listing", listing: @listing
|
||||
|
||||
json.partial! "api/v0/shared/user", user: @listing.user
|
||||
|
||||
|
|
|
|||
5
app/views/api/v0/organizations/listings.json.jbuilder
Normal file
5
app/views/api/v0/organizations/listings.json.jbuilder
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
json.array! @listings do |listing|
|
||||
json.partial! "api/v0/shared/listing", listing: listing
|
||||
json.partial! "api/v0/shared/user", user: listing.user
|
||||
json.partial! "api/v0/shared/organization", organization: @organization
|
||||
end
|
||||
|
|
@ -201,6 +201,7 @@ Rails.application.routes.draw do
|
|||
resources :profile_images, only: %i[show], param: :username
|
||||
resources :organizations, only: [:show], param: :username do
|
||||
resources :users, only: [:index], to: "organizations#users"
|
||||
resources :listings, only: [:index], to: "organizations#listings"
|
||||
end
|
||||
|
||||
namespace :admin do
|
||||
|
|
|
|||
106
docs/api_v0.yml
106
docs/api_v0.yml
|
|
@ -17,7 +17,7 @@ info:
|
|||
Dates and date times, unless otherwise specified, must be in
|
||||
the [RFC 3339](https://tools.ietf.org/html/rfc3339) format.
|
||||
|
||||
version: "0.9.3"
|
||||
version: "0.9.4"
|
||||
termsOfService: https://dev.to/terms
|
||||
contact:
|
||||
name: DEV Team
|
||||
|
|
@ -99,6 +99,15 @@ components:
|
|||
minimum: 1
|
||||
maximum: 1000
|
||||
default: 80
|
||||
listingCategoryParam:
|
||||
name: category
|
||||
in: query
|
||||
description: |
|
||||
Using this parameter will return listings belonging to the
|
||||
requested category.
|
||||
schema:
|
||||
type: string
|
||||
example: cfp
|
||||
|
||||
securitySchemes:
|
||||
api_key:
|
||||
|
|
@ -1580,6 +1589,39 @@ components:
|
|||
website_url:
|
||||
profile_image: https://res.cloudinary.com/practicaldev/image/fetch/s--ggU5WPaT--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/136256/11cced64-afd2-4421-91e1-c5f7b216d49b.jpeg
|
||||
profile_image_90: https://res.cloudinary.com/practicaldev/image/fetch/s--CjladMBD--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/136256/11cced64-afd2-4421-91e1-c5f7b216d49b.jpeg
|
||||
ListingsByOrganization:
|
||||
value:
|
||||
- type_of: listing
|
||||
id: 1157
|
||||
title: TestBash Detroit
|
||||
slug: testbash-detroit-50gb
|
||||
body_markdown: "Do you want to learn about automation? Maybe you're interested in
|
||||
AI-driven testing? Security testing? We have a selection of talks, workshops and
|
||||
training courses to help you in a wide variety of areas in software testing. Join
|
||||
us for our very first trip to Detroit MI! \n\nhttps://bit.ly/TBDetroit"
|
||||
tag_list: events
|
||||
tags:
|
||||
- events
|
||||
category: cfp
|
||||
processed_html: |
|
||||
<p>Do you want to learn about automation? Maybe you're interested in AI-driven testing? Security testing? We have a selection of talks, workshops and training courses to help you in a wide variety of areas in software testing. Join us for our very first trip to Detroit MI! </p>
|
||||
|
||||
<p><a href="https://bit.ly/TBDetroit">https://bit.ly/TBDetroit</a></p>
|
||||
published: true
|
||||
user:
|
||||
name: Heather
|
||||
username: heatherr
|
||||
twitter_username:
|
||||
github_username: Heather-R
|
||||
website_url:
|
||||
profile_image: https://res.cloudinary.com/practicaldev/image/fetch/s--ggU5WPaT--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/136256/11cced64-afd2-4421-91e1-c5f7b216d49b.jpeg
|
||||
profile_image_90: https://res.cloudinary.com/practicaldev/image/fetch/s--CjladMBD--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/136256/11cced64-afd2-4421-91e1-c5f7b216d49b.jpeg
|
||||
organization:
|
||||
name: E Corp
|
||||
username: ecorp
|
||||
slug: ecorp
|
||||
profile_image: https://res.cloudinary.com/practicaldev/image/fetch/s--ggU5WPaT--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/136256/11cced64-afd2-4421-91e1-c5f7b216d49b.jpeg
|
||||
profile_image_90: https://res.cloudinary.com/practicaldev/image/fetch/s--CjladMBD--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/136256/11cced64-afd2-4421-91e1-c5f7b216d49b.jpeg
|
||||
Listing:
|
||||
value:
|
||||
type_of: listing
|
||||
|
|
@ -2674,14 +2716,7 @@ paths:
|
|||
parameters:
|
||||
- $ref: '#/components/parameters/pageParam'
|
||||
- $ref: '#/components/parameters/perPageParam30to1000'
|
||||
- name: category
|
||||
in: query
|
||||
description: |
|
||||
Using this parameter will return listings belonging to the
|
||||
requested category.
|
||||
schema:
|
||||
type: string
|
||||
example: cfp
|
||||
- $ref: '#/components/parameters/listingCategoryParam'
|
||||
responses:
|
||||
"200":
|
||||
description: A list of listings
|
||||
|
|
@ -3128,6 +3163,59 @@ paths:
|
|||
source: |
|
||||
curl https://dev.to/api/organizations/ecorp/users
|
||||
|
||||
/organizations/{username}/listings:
|
||||
get:
|
||||
operationId: getOrgListings
|
||||
summary: Organization's listings
|
||||
description: |
|
||||
This endpoint allows the client to retrieve a list of listings belonging to the organization
|
||||
|
||||
It supports pagination, each page will contain `30` listing by default.
|
||||
tags:
|
||||
- organizations
|
||||
parameters:
|
||||
- name: username
|
||||
in: path
|
||||
required: true
|
||||
description: |
|
||||
Username of the organization
|
||||
schema:
|
||||
type: string
|
||||
example: "ecorp"
|
||||
- $ref: '#/components/parameters/pageParam'
|
||||
- $ref: '#/components/parameters/perPageParam30to1000'
|
||||
- $ref: '#/components/parameters/listingCategoryParam'
|
||||
responses:
|
||||
"200":
|
||||
description: A list of listings belonging to the organization
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/Listing"
|
||||
- required:
|
||||
- organization
|
||||
examples:
|
||||
user-success:
|
||||
$ref: "#/components/examples/ListingsByOrganization"
|
||||
"404":
|
||||
description: Resource not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/APIError"
|
||||
examples:
|
||||
user-not-found:
|
||||
$ref: "#/components/examples/ErrorNotFound"
|
||||
x-code-samples:
|
||||
- lang: Shell
|
||||
label: curl
|
||||
source: |
|
||||
curl https://dev.to/api/organizations/ecorp/listings
|
||||
|
||||
|
||||
/podcast_episodes:
|
||||
get:
|
||||
operationId: getPodcastEpisodes
|
||||
|
|
|
|||
|
|
@ -65,4 +65,49 @@ RSpec.describe "Api::V0::Organizations", type: :request do
|
|||
expect(response_org_users["profile_image"]).to eq(Images::Profile.call(org_user.profile_image_url, length: 320))
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/organizations/:username/listings" do
|
||||
let(:org_user) { create(:user, :org_member) }
|
||||
let(:organization) { org_user.organizations.first }
|
||||
let!(:listing) { create(:listing, user: org_user, organization: organization) }
|
||||
|
||||
it "returns 404 if the organizations username is not found" do
|
||||
get "/api/organizations/invalid-username/listings"
|
||||
expect(response).to have_http_status(:not_found)
|
||||
end
|
||||
|
||||
it "supports pagination" do
|
||||
create(:listing, user: org_user, organization: organization)
|
||||
|
||||
get api_organization_listings_path(organization.username), params: { page: 1, per_page: 1 }
|
||||
expect(response.parsed_body.length).to eq(1)
|
||||
|
||||
get api_organization_listings_path(organization.username), params: { page: 2, per_page: 1 }
|
||||
expect(response.parsed_body.length).to eq(1)
|
||||
|
||||
get api_organization_listings_path(organization.username), params: { page: 3, per_page: 1 }
|
||||
expect(response.parsed_body.length).to eq(0)
|
||||
end
|
||||
|
||||
it "returns the correct json representation of the organizations listings", :aggregate_failures do
|
||||
get api_organization_listings_path(organization.username)
|
||||
response_listing = response.parsed_body.first
|
||||
expect(response_listing["type_of"]).to eq("listing")
|
||||
|
||||
%w[id title slug body_markdown category processed_html published listing_category_id].each do |attr|
|
||||
expect(response_listing[attr]).to eq(listing.public_send(attr))
|
||||
end
|
||||
|
||||
expect(response_listing["tag_list"]).to eq(listing.cached_tag_list)
|
||||
expect(response_listing["tags"]).to match_array(listing.tag_list)
|
||||
|
||||
%w[name username twitter_username github_username website_url].each do |attr|
|
||||
expect(response_listing["user"][attr]).to eq(org_user.public_send(attr))
|
||||
end
|
||||
|
||||
%w[name username slug].each do |attr|
|
||||
expect(response_listing["organization"][attr]).to eq(organization.public_send(attr))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue