DisplayAd API: Read endpoints (#18726)

* DisplayAd API: Read endpoints

* Updated swagger docs for display_ads read endpoints

* Update swagger with type_of

* Remove extraneous comment

* Restrict authentication to strictly api_key
This commit is contained in:
Joshua Wehner 2022-11-21 09:31:01 +01:00 committed by GitHub
parent db39270539
commit 7c82d128ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 322 additions and 30 deletions

View file

@ -0,0 +1,25 @@
module Api
module V1
class DisplayAdsController < ApiController
before_action :authenticate_with_api_key!
before_action :require_admin
def index
@display_ads = DisplayAd.order(id: :desc).page(params[:page]).per(50)
@display_ads = @display_ads.search_ads(params[:search]) if params[:search].present?
render json: @display_ads
end
def show
@display_ad = DisplayAd.find(params[:id])
render json: @display_ad
end
private
def require_admin
authorize DisplayAd, :access?, policy_class: InternalPolicy
end
end
end
end

View file

@ -51,6 +51,8 @@ Rails.application.routes.draw do
post "/reactions", to: "reactions#create"
post "/reactions/toggle", to: "reactions#toggle"
resources :display_ads, only: %i[index show]
draw :api
end

View file

@ -0,0 +1,65 @@
require "rails_helper"
# rubocop:disable RSpec/InstanceVariable
RSpec.describe "Api::V1::DisplayAds" do
let!(:v1_headers) { { "content-type" => "application/json", "Accept" => "application/vnd.forem.api-v1+json" } }
before do
allow(FeatureFlag).to receive(:enabled?).with(:api_v1).and_return(true)
@ad1 = create(:display_ad, published: true, approved: true)
end
shared_context "when user is authorized" do
let(:api_secret) { create(:api_secret) }
let(:user) { api_secret.user }
let(:auth_header) { v1_headers.merge({ "api-key" => api_secret.secret }) }
before { user.add_role(:admin) }
end
context "when authenticated and authorized and get to index" do
include_context "when user is authorized"
describe "GET /api/display_ads" do
it "returns json response" do
get api_display_ads_path, headers: auth_header
expect(response).to have_http_status(:success)
expect(response.media_type).to eq("application/json")
expect(response.parsed_body.size).to eq(1)
end
end
describe "GET /api/display_ads/:id" do
it "returns json response" do
get api_display_ad_path(@ad1.id), headers: auth_header
expect(response).to have_http_status(:success)
expect(response.media_type).to eq("application/json")
expect(response.parsed_body).to include(
"id" => @ad1.id,
"published" => true,
"approved" => true,
"cached_tag_list" => "",
"clicks_count" => 0,
)
end
end
end
context "when unauthenticated and get to index" do
it "returns unauthorized" do
get api_display_ads_path, headers: v1_headers
expect(response).to have_http_status(:unauthorized)
end
end
context "when unauthorized and get to show" do
it "returns unauthorized" do
get api_display_ads_path, params: { id: @ad1.id },
headers: v1_headers.merge({ "api-key" => "invalid api key" })
expect(response).to have_http_status(:unauthorized)
end
end
end
# rubocop:enable RSpec/InstanceVariable

View file

@ -0,0 +1,91 @@
require "rails_helper"
require "swagger_helper"
# rubocop:disable RSpec/EmptyExampleGroup
# rubocop:disable RSpec/VariableName
RSpec.describe "api/v1/display_ads" do
let(:Accept) { "application/vnd.forem.api-v1+json" }
let(:api_secret) { create(:api_secret) }
let(:display_ad) { create(:display_ad) }
let(:user) { api_secret.user }
before { user.add_role(:admin) }
path "/api/display_ads" do
describe "get all display ads" do
get("display ads") do
tags "display ads"
description(<<-DESCRIBE.strip)
This endpoint allows the client to retrieve a list of all display ads.
DESCRIBE
produces "application/json"
response(200, "successful") do
let(:"api-key") { api_secret.secret }
add_examples
run_test!
end
response "401", "unauthorized" do
let(:"api-key") { "invalid" }
add_examples
run_test!
end
end
end
end
path "/api/display_ads/{id}" do
describe "GET a single display ad" do
get("display ad") do
tags "display ads"
description(<<-DESCRIBE.strip)
This endpoint allows the client to retrieve a single display ad, via its id.
DESCRIBE
produces "application/json"
parameter name: :id,
in: :path,
required: true,
description: "The ID of the user to unpublish.",
schema: {
type: :integer,
format: :int32,
minimum: 1
},
example: 123
response(200, "successful") do
let(:id) { display_ad.id }
let(:"api-key") { api_secret.secret }
add_examples
run_test!
end
response "401", "unauthorized" do
let(:"api-key") { "invalid" }
let(:id) { 10_000 }
add_examples
run_test!
end
response "404", "Unknown DisplayAd ID" do
let(:"api-key") { api_secret.secret }
let(:id) { 10_000 }
add_examples
run_test!
end
end
end
end
end
# rubocop:enable RSpec/VariableName
# rubocop:enable RSpec/EmptyExampleGroup

View file

@ -103,45 +103,45 @@
"example": [
{
"type_of": "article",
"id": 1,
"title": "Mr Standfast1",
"description": "Fixie crucifix vegan vinyl cleanse stumptown. Ennui chicharrones actually heirloom taxidermy selfies...",
"readable_publish_date": "Sep 3",
"slug": "mr-standfast1-o1j",
"path": "/username1/mr-standfast1-o1j",
"url": "http://localhost:3000/username1/mr-standfast1-o1j",
"id": 191,
"title": "An Acceptable Time172",
"description": "Shabby chic wayfarers normcore marfa carry. Schlitz kinfolk +1 kickstarter pitchfork cardigan 90's....",
"readable_publish_date": "Nov 17",
"slug": "an-acceptable-time172-25ik",
"path": "/username378/an-acceptable-time172-25ik",
"url": "http://localhost:3000/username378/an-acceptable-time172-25ik",
"comments_count": 0,
"public_reactions_count": 0,
"collection_id": null,
"published_timestamp": "2022-09-02T19:24:23Z",
"published_timestamp": "2022-11-17T09:30:31Z",
"positive_reactions_count": 0,
"cover_image": "http://localhost:3000/assets/19-ed58d3e8defcefc445020631589697a05e725243e834b5192aee4e6b91a3e927.png",
"social_image": "http://localhost:3000/assets/19-ed58d3e8defcefc445020631589697a05e725243e834b5192aee4e6b91a3e927.png",
"canonical_url": "http://localhost:3000/username1/mr-standfast1-o1j",
"created_at": "2022-09-02T19:24:24Z",
"cover_image": "http://localhost:3000/assets/23-33ea5a2b5af3dc15b9ed90de0b850f67e2390eaec3361d6687d3b9750c699f84.png",
"social_image": "http://localhost:3000/assets/23-33ea5a2b5af3dc15b9ed90de0b850f67e2390eaec3361d6687d3b9750c699f84.png",
"canonical_url": "http://localhost:3000/username378/an-acceptable-time172-25ik",
"created_at": "2022-11-17T09:30:31Z",
"edited_at": null,
"crossposted_at": null,
"published_at": "2022-09-02T19:24:23Z",
"last_comment_at": "2022-09-02T19:24:23Z",
"published_at": "2022-11-17T09:30:31Z",
"last_comment_at": "2022-11-17T09:30:31Z",
"reading_time_minutes": 1,
"tag_list": ["discuss"],
"tags": "discuss",
"user": {
"name": "Lavona \"Lenna\" \\:/ Watsica",
"username": "username1",
"twitter_username": "twitter1",
"github_username": "github1",
"user_id": 1,
"name": "Melaine \"Quinn\" \\:/ Goodwin",
"username": "username378",
"twitter_username": "twitter378",
"github_username": "github378",
"user_id": 386,
"website_url": null,
"profile_image": "/uploads/user/profile_image/1/4e030890-caa7-4c5b-836a-432e7b1bfdfc.jpeg",
"profile_image_90": "/uploads/user/profile_image/1/4e030890-caa7-4c5b-836a-432e7b1bfdfc.jpeg"
"profile_image": "/uploads/user/profile_image/386/93f316c4-1287-45b3-b1c7-49861128e477.jpeg",
"profile_image_90": "/uploads/user/profile_image/386/93f316c4-1287-45b3-b1c7-49861128e477.jpeg"
},
"organization": {
"name": "Weimann-Toy",
"username": "org1",
"slug": "org1",
"profile_image": "/uploads/organization/profile_image/1/2df76e17-f420-434a-8027-2e90ebfb89bf.png",
"profile_image_90": "/uploads/organization/profile_image/1/2df76e17-f420-434a-8027-2e90ebfb89bf.png"
"name": "Baumbach Group",
"username": "org60",
"slug": "org60",
"profile_image": "/uploads/organization/profile_image/65/0bccbabf-3f76-473b-8c5e-bac56178315b.png",
"profile_image_90": "/uploads/organization/profile_image/65/0bccbabf-3f76-473b-8c5e-bac56178315b.png"
},
"flare_tag": {
"name": "discuss",
@ -180,6 +180,16 @@
"minimum": 1
},
"example": 1
},
{
"name": "note",
"in": "query",
"required": false,
"description": "Content for the note that's created along with unpublishing",
"schema": {
"type": "string"
},
"example": "Admin requested unpublishing all articles via API"
}
],
"responses": {
@ -211,6 +221,105 @@
}
}
},
"/api/display_ads": {
"get": {
"summary": "display ads",
"tags": ["display ads"],
"description": "This endpoint allows the client to retrieve a list of all display ads.",
"responses": {
"200": {
"description": "successful",
"content": {
"application/json": {
"example": []
}
}
},
"401": {
"description": "unauthorized",
"content": {
"application/json": {
"example": {
"error": "unauthorized",
"status": 401
}
}
}
}
}
}
},
"/api/display_ads/{id}": {
"get": {
"summary": "display ad",
"tags": ["display ads"],
"description": "This endpoint allows the client to retrieve a single display ad, via its id.",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"description": "The ID of the user to unpublish.",
"schema": {
"type": "integer",
"format": "int32",
"minimum": 1
},
"example": 123
}
],
"responses": {
"200": {
"description": "successful",
"content": {
"application/json": {
"example": {
"id": 10,
"approved": false,
"body_markdown": "Hello _hey_ Hey hey 5",
"cached_tag_list": "",
"clicks_count": 0,
"created_at": "2022-11-17T10:30:32.066+01:00",
"display_to": "all",
"impressions_count": 0,
"name": "Display Ad 10",
"organization_id": 66,
"placement_area": "sidebar_left",
"processed_html": "<p>Hello <em>hey</em> Hey hey 5</p>",
"published": false,
"success_rate": 0.0,
"type_of": "in_house",
"updated_at": "2022-11-17T10:30:32.069+01:00",
"tag_list": []
}
}
}
},
"401": {
"description": "unauthorized",
"content": {
"application/json": {
"example": {
"error": "unauthorized",
"status": 401
}
}
}
},
"404": {
"description": "Unknown DisplayAd ID",
"content": {
"application/json": {
"example": {
"error": "not found",
"status": 404
}
}
}
}
}
}
},
"/api/reactions/toggle": {
"post": {
"summary": "toggle reaction",
@ -223,7 +332,7 @@
"required": true,
"schema": {
"type": "string",
"enum": ["like", "readinglist", "unicorn", "thinking", "hands"]
"enum": ["like", "readinglist", "unicorn"]
}
},
{
@ -254,7 +363,7 @@
"result": "create",
"category": "like",
"id": 1,
"reactable_id": 5,
"reactable_id": 195,
"reactable_type": "Article"
}
}
@ -286,7 +395,7 @@
"required": true,
"schema": {
"type": "string",
"enum": ["like", "readinglist", "unicorn", "thinking", "hands"]
"enum": ["like", "readinglist", "unicorn"]
}
},
{
@ -317,7 +426,7 @@
"result": "none",
"category": "like",
"id": 3,
"reactable_id": 7,
"reactable_id": 197,
"reactable_type": "Article"
}
}