From 4db8230194258a9a1c3d17d7261608515f3f2067 Mon Sep 17 00:00:00 2001 From: Robert R George Date: Tue, 18 Apr 2023 02:33:30 -0700 Subject: [PATCH] Add trend management to admin API (#24257) --- .../preview_card_providers_controller.rb | 72 +++++++++++++++++++ .../api/v1/admin/trends/links_controller.rb | 31 +++++++- .../v1/admin/trends/statuses_controller.rb | 31 +++++++- .../api/v1/admin/trends/tags_controller.rb | 23 +++++- app/models/preview_card_provider.rb | 1 + .../rest/admin/trends/link_serializer.rb | 9 +++ .../links/preview_card_provider_serializer.rb | 10 +++ .../rest/admin/trends/status_serializer.rb | 9 +++ config/routes.rb | 30 +++++++- .../preview_card_providers_controller_spec.rb | 68 ++++++++++++++++++ .../v1/admin/trends/links_controller_spec.rb | 49 ++++++++++++- .../admin/trends/statuses_controller_spec.rb | 49 ++++++++++++- .../v1/admin/trends/tags_controller_spec.rb | 49 ++++++++++++- 13 files changed, 419 insertions(+), 12 deletions(-) create mode 100644 app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb create mode 100644 app/serializers/rest/admin/trends/link_serializer.rb create mode 100644 app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb create mode 100644 app/serializers/rest/admin/trends/status_serializer.rb create mode 100644 spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb diff --git a/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb b/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb new file mode 100644 index 000000000..5d9fcc82c --- /dev/null +++ b/app/controllers/api/v1/admin/trends/links/preview_card_providers_controller.rb @@ -0,0 +1,72 @@ +# frozen_string_literal: true + +class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseController + include Authorization + + LIMIT = 100 + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + before_action :set_providers, only: :index + + after_action :verify_authorized + after_action :insert_pagination_headers, only: :index + + PAGINATION_PARAMS = %i(limit).freeze + + def index + authorize :preview_card_provider, :index? + + render json: @providers, each_serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer + end + + def approve + authorize :preview_card_provider, :review? + + provider = PreviewCardProvider.find(params[:id]) + provider.update(trendable: true, reviewed_at: Time.now.utc) + render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer + end + + def reject + authorize :preview_card_provider, :review? + + provider = PreviewCardProvider.find(params[:id]) + provider.update(trendable: false, reviewed_at: Time.now.utc) + render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer + end + + private + + def set_providers + @providers = PreviewCardProvider.all.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id)) + end + + def insert_pagination_headers + set_pagination_headers(next_path, prev_path) + end + + def next_path + api_v1_admin_trends_links_preview_card_providers_url(pagination_params(max_id: pagination_max_id)) if records_continue? + end + + def prev_path + api_v1_admin_trends_links_preview_card_providers_url(pagination_params(min_id: pagination_since_id)) unless @providers.empty? + end + + def pagination_max_id + @providers.last.id + end + + def pagination_since_id + @providers.first.id + end + + def records_continue? + @providers.size == limit_param(LIMIT) + end + + def pagination_params(core_params) + params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params) + end +end diff --git a/app/controllers/api/v1/admin/trends/links_controller.rb b/app/controllers/api/v1/admin/trends/links_controller.rb index cc6388980..7f4ca4828 100644 --- a/app/controllers/api/v1/admin/trends/links_controller.rb +++ b/app/controllers/api/v1/admin/trends/links_controller.rb @@ -1,7 +1,36 @@ # frozen_string_literal: true class Api::V1::Admin::Trends::LinksController < Api::V1::Trends::LinksController - before_action -> { authorize_if_got_token! :'admin:read' } + include Authorization + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + + after_action :verify_authorized, except: :index + + def index + if current_user&.can?(:manage_taxonomies) + render json: @links, each_serializer: REST::Admin::Trends::LinkSerializer + else + super + end + end + + def approve + authorize :preview_card, :review? + + link = PreviewCard.find(params[:id]) + link.update(trendable: true) + render json: link, serializer: REST::Admin::Trends::LinkSerializer + end + + def reject + authorize :preview_card, :review? + + link = PreviewCard.find(params[:id]) + link.update(trendable: false) + render json: link, serializer: REST::Admin::Trends::LinkSerializer + end private diff --git a/app/controllers/api/v1/admin/trends/statuses_controller.rb b/app/controllers/api/v1/admin/trends/statuses_controller.rb index c39f77363..34b6580df 100644 --- a/app/controllers/api/v1/admin/trends/statuses_controller.rb +++ b/app/controllers/api/v1/admin/trends/statuses_controller.rb @@ -1,7 +1,36 @@ # frozen_string_literal: true class Api::V1::Admin::Trends::StatusesController < Api::V1::Trends::StatusesController - before_action -> { authorize_if_got_token! :'admin:read' } + include Authorization + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + + after_action :verify_authorized, except: :index + + def index + if current_user&.can?(:manage_taxonomies) + render json: @statuses, each_serializer: REST::Admin::Trends::StatusSerializer + else + super + end + end + + def approve + authorize [:admin, :status], :review? + + status = Status.find(params[:id]) + status.update(trendable: true) + render json: status, serializer: REST::Admin::Trends::StatusSerializer + end + + def reject + authorize [:admin, :status], :review? + + status = Status.find(params[:id]) + status.update(trendable: false) + render json: status, serializer: REST::Admin::Trends::StatusSerializer + end private diff --git a/app/controllers/api/v1/admin/trends/tags_controller.rb b/app/controllers/api/v1/admin/trends/tags_controller.rb index e77df3021..2eeea9522 100644 --- a/app/controllers/api/v1/admin/trends/tags_controller.rb +++ b/app/controllers/api/v1/admin/trends/tags_controller.rb @@ -1,7 +1,12 @@ # frozen_string_literal: true class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController - before_action -> { authorize_if_got_token! :'admin:read' } + include Authorization + + before_action -> { authorize_if_got_token! :'admin:read' }, only: :index + before_action -> { authorize_if_got_token! :'admin:write' }, except: :index + + after_action :verify_authorized, except: :index def index if current_user&.can?(:manage_taxonomies) @@ -11,6 +16,22 @@ class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController end end + def approve + authorize :tag, :review? + + tag = Tag.find(params[:id]) + tag.update(trendable: true, reviewed_at: Time.now.utc) + render json: tag, serializer: REST::Admin::TagSerializer + end + + def reject + authorize :tag, :review? + + tag = Tag.find(params[:id]) + tag.update(trendable: false, reviewed_at: Time.now.utc) + render json: tag, serializer: REST::Admin::TagSerializer + end + private def enabled? diff --git a/app/models/preview_card_provider.rb b/app/models/preview_card_provider.rb index 1dd95fc91..9f5f6d3cb 100644 --- a/app/models/preview_card_provider.rb +++ b/app/models/preview_card_provider.rb @@ -18,6 +18,7 @@ # class PreviewCardProvider < ApplicationRecord + include Paginable include DomainNormalizable include Attachmentable diff --git a/app/serializers/rest/admin/trends/link_serializer.rb b/app/serializers/rest/admin/trends/link_serializer.rb new file mode 100644 index 000000000..c93e6c178 --- /dev/null +++ b/app/serializers/rest/admin/trends/link_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class REST::Admin::Trends::LinkSerializer < REST::Trends::LinkSerializer + attributes :id, :requires_review + + def requires_review + object.requires_review? + end +end diff --git a/app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb b/app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb new file mode 100644 index 000000000..fba0259fb --- /dev/null +++ b/app/serializers/rest/admin/trends/links/preview_card_provider_serializer.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class REST::Admin::Trends::Links::PreviewCardProviderSerializer < ActiveModel::Serializer + attributes :id, :domain, :trendable, :reviewed_at, + :requested_review_at, :requires_review + + def requires_review + object.requires_review? + end +end diff --git a/app/serializers/rest/admin/trends/status_serializer.rb b/app/serializers/rest/admin/trends/status_serializer.rb new file mode 100644 index 000000000..e46be30ab --- /dev/null +++ b/app/serializers/rest/admin/trends/status_serializer.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class REST::Admin::Trends::StatusSerializer < REST::StatusSerializer + attributes :requires_review + + def requires_review + object.requires_review? + end +end diff --git a/config/routes.rb b/config/routes.rb index 22ef10866..3be088cee 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -660,9 +660,33 @@ Rails.application.routes.draw do resources :ip_blocks, only: [:index, :show, :update, :create, :destroy] namespace :trends do - resources :tags, only: [:index] - resources :links, only: [:index] - resources :statuses, only: [:index] + resources :tags, only: [:index] do + member do + post :approve + post :reject + end + end + resources :links, only: [:index] do + member do + post :approve + post :reject + end + end + resources :statuses, only: [:index] do + member do + post :approve + post :reject + end + end + + namespace :links do + resources :preview_card_providers, only: [:index], path: :publishers do + member do + post :approve + post :reject + end + end + end end post :measures, to: 'measures#create' diff --git a/spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb b/spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb new file mode 100644 index 000000000..883a55b7b --- /dev/null +++ b/spec/controllers/api/v1/admin/trends/links/preview_card_providers_controller_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Api::V1::Admin::Trends::Links::PreviewCardProvidersController do + render_views + + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:account) { Fabricate(:account) } + let(:preview_card_provider) { Fabricate(:preview_card_provider) } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + describe 'GET #index' do + it 'returns http success' do + get :index, params: { account_id: account.id, limit: 2 } + + expect(response).to have_http_status(200) + end + end + + describe 'POST #approve' do + before do + post :approve, params: { id: preview_card_provider.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: preview_card_provider.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end +end diff --git a/spec/controllers/api/v1/admin/trends/links_controller_spec.rb b/spec/controllers/api/v1/admin/trends/links_controller_spec.rb index a64292f06..9c144d3fa 100644 --- a/spec/controllers/api/v1/admin/trends/links_controller_spec.rb +++ b/spec/controllers/api/v1/admin/trends/links_controller_spec.rb @@ -5,14 +5,33 @@ require 'rails_helper' describe Api::V1::Admin::Trends::LinksController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:account) { Fabricate(:account) } + let(:preview_card) { Fabricate(:preview_card) } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do it 'returns http success' do get :index, params: { account_id: account.id, limit: 2 } @@ -20,4 +39,30 @@ describe Api::V1::Admin::Trends::LinksController do expect(response).to have_http_status(200) end end + + describe 'POST #approve' do + before do + post :approve, params: { id: preview_card.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: preview_card.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end end diff --git a/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb b/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb index 821cc499f..d25186b37 100644 --- a/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb +++ b/spec/controllers/api/v1/admin/trends/statuses_controller_spec.rb @@ -5,14 +5,33 @@ require 'rails_helper' describe Api::V1::Admin::Trends::StatusesController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:account) { Fabricate(:account) } + let(:status) { Fabricate(:status) } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do it 'returns http success' do get :index, params: { account_id: account.id, limit: 2 } @@ -20,4 +39,30 @@ describe Api::V1::Admin::Trends::StatusesController do expect(response).to have_http_status(200) end end + + describe 'POST #approve' do + before do + post :approve, params: { id: status.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: status.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end end diff --git a/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb b/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb index 480306ce7..5ee443d57 100644 --- a/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb +++ b/spec/controllers/api/v1/admin/trends/tags_controller_spec.rb @@ -5,14 +5,33 @@ require 'rails_helper' describe Api::V1::Admin::Trends::TagsController do render_views - let(:user) { Fabricate(:user, role: UserRole.find_by(name: 'Admin')) } - let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'admin:read') } + let(:role) { UserRole.find_by(name: 'Admin') } + let(:user) { Fabricate(:user, role: role) } + let(:scopes) { 'admin:read admin:write' } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } let(:account) { Fabricate(:account) } + let(:tag) { Fabricate(:tag) } before do allow(controller).to receive(:doorkeeper_token) { token } end + shared_examples 'forbidden for wrong scope' do |wrong_scope| + let(:scopes) { wrong_scope } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + + shared_examples 'forbidden for wrong role' do |wrong_role| + let(:role) { UserRole.find_by(name: wrong_role) } + + it 'returns http forbidden' do + expect(response).to have_http_status(403) + end + end + describe 'GET #index' do it 'returns http success' do get :index, params: { account_id: account.id, limit: 2 } @@ -20,4 +39,30 @@ describe Api::V1::Admin::Trends::TagsController do expect(response).to have_http_status(200) end end + + describe 'POST #approve' do + before do + post :approve, params: { id: tag.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end + + describe 'POST #reject' do + before do + post :reject, params: { id: tag.id } + end + + it_behaves_like 'forbidden for wrong scope', 'write:statuses' + it_behaves_like 'forbidden for wrong role', '' + + it 'returns http success' do + expect(response).to have_http_status(200) + end + end end