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:
Spencer 2020-12-18 09:20:35 -08:00 committed by GitHub
parent 908ab99939
commit 6e2aab623e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 170 additions and 12 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -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