Documentation for Negative Behavior APIs (#18187)
Co-authored-by: Suzanne Aitchison <suzanne@forem.com>
This commit is contained in:
parent
582d78a7ff
commit
74e44aeb61
7 changed files with 392 additions and 38 deletions
|
|
@ -112,7 +112,7 @@ module Api
|
|||
payload = { action: "api_article_unpublish", article_id: @article.id }
|
||||
Audit::Logger.log(:admin_api, @user, payload)
|
||||
|
||||
render head: :ok
|
||||
render status: :no_content
|
||||
else
|
||||
render json: { message: @article.errors.full_messages }, status: :unprocessable_entity
|
||||
end
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ module Api
|
|||
payload = { action: "api_user_suspend", target_user_id: target_user.id }
|
||||
Audit::Logger.log(:admin_api, @user, payload)
|
||||
|
||||
render head: :ok
|
||||
render status: :no_content
|
||||
rescue StandardError
|
||||
render json: {
|
||||
success: false,
|
||||
|
|
@ -67,7 +67,7 @@ module Api
|
|||
}
|
||||
Audit::Logger.log(:admin_api, @user, payload)
|
||||
|
||||
render head: :ok
|
||||
render status: :no_content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1217,7 +1217,7 @@ RSpec.describe "Api::V1::Articles", type: :request do
|
|||
it "unpublishes an article" do
|
||||
expect(published_article.published).to be true
|
||||
put path, headers: v1_headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(published_article.reload.published).to be false
|
||||
end
|
||||
end
|
||||
|
|
@ -1228,7 +1228,7 @@ RSpec.describe "Api::V1::Articles", type: :request do
|
|||
it "unpublishes an article" do
|
||||
expect(published_article.published).to be true
|
||||
put path, headers: v1_headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(published_article.reload.published).to be false
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,28 @@
|
|||
require "rails_helper"
|
||||
require "swagger_helper"
|
||||
|
||||
# rubocop:disable RSpec/EmptyExampleGroup
|
||||
# rubocop:disable RSpec/VariableName
|
||||
|
||||
RSpec.describe "Api::V1::Docs::Articles", type: :request do
|
||||
let(:organization) { create(:organization) } # not used by every spec but lower times overall
|
||||
let(:tag) { create(:tag, :with_colors, name: "discuss") }
|
||||
let(:article) { create(:article, featured: true, tags: "discuss") }
|
||||
let(:article) { create(:article, featured: true, tags: "discuss", published: true) }
|
||||
let(:unpublished_aricle) { create(:article, published: false) }
|
||||
let(:api_secret) { create(:api_secret) }
|
||||
let(:user) { api_secret.user }
|
||||
let(:Accept) { "application/vnd.forem.api-v1+json" }
|
||||
|
||||
before do
|
||||
stub_const("FlareTag::FLARE_TAG_IDS_HASH", { "discuss" => tag.id })
|
||||
allow(FeatureFlag).to receive(:enabled?).with(:api_v1).and_return(true)
|
||||
end
|
||||
|
||||
# rubocop:disable RSpec/EmptyExampleGroup
|
||||
describe "GET /articles" do
|
||||
before do
|
||||
article.update_columns(organization_id: organization.id)
|
||||
end
|
||||
|
||||
# rubocop:disable RSpec/VariableName
|
||||
path "/api/articles" do
|
||||
get "Published articles" do
|
||||
tags "articles"
|
||||
|
|
@ -88,7 +93,7 @@ belonging to the requested collection, ordered by ascending publication date.",
|
|||
example: 99
|
||||
|
||||
response "200", "A List of Articles" do
|
||||
let(:"api-key") { "valid" }
|
||||
let(:"api-key") { api_secret.secret }
|
||||
schema type: :array,
|
||||
items: { "$ref": "#/components/schemas/ArticleIndex" }
|
||||
add_examples
|
||||
|
|
@ -97,13 +102,76 @@ belonging to the requested collection, ordered by ascending publication date.",
|
|||
end
|
||||
|
||||
response "401", "unauthorized" do
|
||||
let(:Accept) { "application/vnd.forem.api-v1+json" }
|
||||
let(:"api-key") { "invalid" }
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable RSpec/VariableName
|
||||
end
|
||||
|
||||
describe "PUT /articles/:id/unpublish" do
|
||||
before do
|
||||
user.add_role(:admin)
|
||||
end
|
||||
|
||||
path "/api/articles/{id}/unpublish" do
|
||||
put "Unpublish an article" do
|
||||
tags "articles"
|
||||
description "This endpoint allows the client to unpublish an article.
|
||||
|
||||
The user associated with the API key must have any 'admin' or 'moderator' role.
|
||||
|
||||
The article will be unpublished and will no longer be visible to the public. It will remain
|
||||
in the database and will set back to draft status on the author's posts dashboard. Any
|
||||
notifications associated with the article will be deleted. Any comments on the article
|
||||
will remain."
|
||||
operationId "unpublishArticle"
|
||||
produces "application/json"
|
||||
parameter name: :id, in: :path, required: true,
|
||||
description: "The ID of the article to unpublish.",
|
||||
schema: {
|
||||
type: :integer,
|
||||
format: :int32,
|
||||
minimum: 1
|
||||
},
|
||||
example: 1
|
||||
|
||||
response "204", "Article successfully unpublished" do
|
||||
let(:"api-key") { api_secret.secret }
|
||||
let(:id) { article.id }
|
||||
add_examples
|
||||
|
||||
run_test!
|
||||
end
|
||||
|
||||
response "401", "Article already unpublished" do
|
||||
let(:"api-key") { api_secret.secret }
|
||||
let(:id) { unpublished_aricle.id }
|
||||
add_examples
|
||||
|
||||
run_test!
|
||||
end
|
||||
|
||||
response "401", "Unauthorized" do
|
||||
let(:regular_user) { create(:user) }
|
||||
let(:low_security_api_secret) { create(:api_secret, user: regular_user) }
|
||||
let(:"api-key") { low_security_api_secret.secret }
|
||||
let(:id) { unpublished_aricle.id }
|
||||
add_examples
|
||||
|
||||
run_test!
|
||||
end
|
||||
|
||||
response "404", "Article Not Found" do
|
||||
let(:"api-key") { api_secret.secret }
|
||||
let(:id) { 0 }
|
||||
add_examples
|
||||
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable RSpec/VariableName
|
||||
# rubocop:enable RSpec/EmptyExampleGroup
|
||||
end
|
||||
|
|
|
|||
139
spec/requests/api/v1/docs/users_spec.rb
Normal file
139
spec/requests/api/v1/docs/users_spec.rb
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
require "rails_helper"
|
||||
require "swagger_helper"
|
||||
|
||||
# rubocop:disable RSpec/EmptyExampleGroup
|
||||
# rubocop:disable RSpec/VariableName
|
||||
|
||||
RSpec.describe "Api::V1::Docs::Users", type: :request do
|
||||
let(:Accept) { "application/vnd.forem.api-v1+json" }
|
||||
let(:api_secret) { create(:api_secret) }
|
||||
let(:user) { api_secret.user }
|
||||
|
||||
let(:banned_user) { create(:user) }
|
||||
let(:article) { create(:article, user: banned_user, published: true) }
|
||||
let(:comment) { create(:comment, user: banned_user, article: article) }
|
||||
|
||||
before do
|
||||
allow(FeatureFlag).to receive(:enabled?).with(:api_v1).and_return(true)
|
||||
user.add_role(:admin)
|
||||
end
|
||||
|
||||
describe "PUT /users/:id/unpublish" do
|
||||
before do
|
||||
user.add_role(:admin)
|
||||
end
|
||||
|
||||
path "/api/users/{id}/unpublish" do
|
||||
put "Unpublish a User's Articles and Comments" do
|
||||
tags "users"
|
||||
description "This endpoint allows the client to unpublish all of the articles and
|
||||
comments created by a user.
|
||||
|
||||
The user associated with the API key must have any 'admin' or 'moderator' role.
|
||||
|
||||
This specified user's articles and comments will be unpublished and will no longer be
|
||||
visible to the public. They will remain in the database and will set back to draft status
|
||||
on the specified user's dashboard. Any notifications associated with the specified user's
|
||||
articles and comments will be deleted.
|
||||
|
||||
Note this endpoint unpublishes articles and comments asychronously: it will return a 204 NO CONTENT
|
||||
status code immediately, but the articles and comments will not be unpublished until the
|
||||
request is completed on the server."
|
||||
operationId "unpublishUser"
|
||||
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: 1
|
||||
|
||||
response "204", "User's articles and comments successfully unpublished" do
|
||||
let(:"api-key") { api_secret.secret }
|
||||
let(:id) { banned_user.id }
|
||||
add_examples
|
||||
|
||||
run_test!
|
||||
end
|
||||
|
||||
response "401", "Unauthorized" do
|
||||
let(:regular_user) { create(:user) }
|
||||
let(:low_security_api_secret) { create(:api_secret, user: regular_user) }
|
||||
let(:"api-key") { low_security_api_secret.secret }
|
||||
let(:id) { banned_user.id }
|
||||
add_examples
|
||||
|
||||
run_test!
|
||||
end
|
||||
|
||||
response "404", "Unknown User ID (still accepted for async processing)" do
|
||||
let(:"api-key") { api_secret.secret }
|
||||
let(:id) { 10_000 }
|
||||
add_examples
|
||||
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT /users/:id/suspend" do
|
||||
before do
|
||||
user.add_role(:admin)
|
||||
end
|
||||
|
||||
path "/api/users/{id}/suspend" do
|
||||
put "Suspend a User" do
|
||||
tags "users"
|
||||
description "This endpoint allows the client to suspend a user.
|
||||
|
||||
The user associated with the API key must have any 'admin' or 'moderator' role.
|
||||
|
||||
This specified user will be assigned the 'suspended' role. Suspending a user will stop the
|
||||
user from posting new posts and comments. It doesn't delete any of the user's content, just
|
||||
prevents them from creating new content while suspended. Users are not notified of their suspension
|
||||
in the UI, so if you want them to know about this, you must notify them."
|
||||
operationId "suspendUser"
|
||||
produces "application/json"
|
||||
parameter name: :id, in: :path, required: true,
|
||||
description: "The ID of the user to suspend.",
|
||||
schema: {
|
||||
type: :integer,
|
||||
format: :int32,
|
||||
minimum: 1
|
||||
},
|
||||
example: 1
|
||||
|
||||
response "204", "User successfully unpublished" do
|
||||
let(:"api-key") { api_secret.secret }
|
||||
let(:id) { banned_user.id }
|
||||
add_examples
|
||||
|
||||
run_test!
|
||||
end
|
||||
|
||||
response "401", "Unauthorized" do
|
||||
let(:regular_user) { create(:user) }
|
||||
let(:low_security_api_secret) { create(:api_secret, user: regular_user) }
|
||||
let(:"api-key") { low_security_api_secret.secret }
|
||||
let(:id) { banned_user.id }
|
||||
add_examples
|
||||
|
||||
run_test!
|
||||
end
|
||||
|
||||
response "404", "Unknown User 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
|
||||
|
|
@ -190,7 +190,7 @@ RSpec.describe "Api::V0::Users", type: :request do
|
|||
params: payload,
|
||||
headers: v1_headers
|
||||
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response).to have_http_status(:no_content)
|
||||
expect(target_user.reload.suspended?).to be true
|
||||
expect(Note.last.content).to eq(payload[:note])
|
||||
end.to change(Note, :count).by(1)
|
||||
|
|
@ -255,7 +255,7 @@ RSpec.describe "Api::V0::Users", type: :request do
|
|||
|
||||
put api_user_unpublish_path(id: target_user.id),
|
||||
headers: v1_headers
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response).to have_http_status(:no_content)
|
||||
|
||||
sidekiq_perform_enqueued_jobs
|
||||
|
||||
|
|
|
|||
|
|
@ -102,44 +102,44 @@
|
|||
"example": [
|
||||
{
|
||||
"type_of": "article",
|
||||
"id": 286,
|
||||
"title": "Terrible Swift Sword1",
|
||||
"description": "Cardigan taxidermy stumptown muggle magic. Tumblr lumbersexual yolo gluten-free taxidermy truffaut...",
|
||||
"readable_publish_date": "Jul 6",
|
||||
"slug": "terrible-swift-sword1-4gja",
|
||||
"path": "/username1/terrible-swift-sword1-4gja",
|
||||
"url": "http://localhost:3000/username1/terrible-swift-sword1-4gja",
|
||||
"id": 2661,
|
||||
"title": "To a God Unknown172",
|
||||
"description": "Thundercats kitsch park bespoke scenester cray irony occupy. Crucifix salvia vegan. Disrupt offal 3...",
|
||||
"readable_publish_date": "Jul 27",
|
||||
"slug": "to-a-god-unknown172-3lh1",
|
||||
"path": "/username469/to-a-god-unknown172-3lh1",
|
||||
"url": "http://localhost:3000/username469/to-a-god-unknown172-3lh1",
|
||||
"comments_count": 0,
|
||||
"public_reactions_count": 0,
|
||||
"collection_id": null,
|
||||
"published_timestamp": "2022-07-06T16:49:01Z",
|
||||
"published_timestamp": "2022-07-27T20:31:54Z",
|
||||
"positive_reactions_count": 0,
|
||||
"cover_image": "http://localhost:3000/assets/12-f9d673ae4ff98002f782ab82c641f2f26673be728e8f5409bea83f2d1de15323.png",
|
||||
"social_image": "http://localhost:3000/assets/12-f9d673ae4ff98002f782ab82c641f2f26673be728e8f5409bea83f2d1de15323.png",
|
||||
"canonical_url": "http://localhost:3000/username1/terrible-swift-sword1-4gja",
|
||||
"created_at": "2022-07-06T16:49:07Z",
|
||||
"cover_image": "http://localhost:3000/assets/19-ed58d3e8defcefc445020631589697a05e725243e834b5192aee4e6b91a3e927.png",
|
||||
"social_image": "http://localhost:3000/assets/19-ed58d3e8defcefc445020631589697a05e725243e834b5192aee4e6b91a3e927.png",
|
||||
"canonical_url": "http://localhost:3000/username469/to-a-god-unknown172-3lh1",
|
||||
"created_at": "2022-07-27T20:31:54Z",
|
||||
"edited_at": null,
|
||||
"crossposted_at": null,
|
||||
"published_at": "2022-07-06T16:49:01Z",
|
||||
"last_comment_at": "2022-07-06T16:49:01Z",
|
||||
"published_at": "2022-07-27T20:31:54Z",
|
||||
"last_comment_at": "2022-07-27T20:31:54Z",
|
||||
"reading_time_minutes": 1,
|
||||
"tag_list": ["discuss"],
|
||||
"tags": "discuss",
|
||||
"user": {
|
||||
"name": "Malorie \"Herman\" \\:/ Trantow",
|
||||
"username": "username1",
|
||||
"twitter_username": "twitter1",
|
||||
"github_username": "github1",
|
||||
"name": "Carey \"Maya\" \\:/ Corwin",
|
||||
"username": "username469",
|
||||
"twitter_username": "twitter469",
|
||||
"github_username": "github469",
|
||||
"website_url": null,
|
||||
"profile_image": "/uploads/user/profile_image/407/bcaaa39b-60d0-4dc2-97d2-9ec5c9e05b47.jpeg",
|
||||
"profile_image_90": "/uploads/user/profile_image/407/bcaaa39b-60d0-4dc2-97d2-9ec5c9e05b47.jpeg"
|
||||
"profile_image": "/uploads/user/profile_image/6332/5475485a-eb89-4e7f-b2d2-c548389462e8.jpeg",
|
||||
"profile_image_90": "/uploads/user/profile_image/6332/5475485a-eb89-4e7f-b2d2-c548389462e8.jpeg"
|
||||
},
|
||||
"organization": {
|
||||
"name": "Stamm Group",
|
||||
"username": "org1",
|
||||
"slug": "org1",
|
||||
"profile_image": "/uploads/organization/profile_image/109/fb157fe5-9a66-4baf-91cb-216f0a206e5e.png",
|
||||
"profile_image_90": "/uploads/organization/profile_image/109/fb157fe5-9a66-4baf-91cb-216f0a206e5e.png"
|
||||
"name": "Blanda, Barton and Tremblay",
|
||||
"username": "org56",
|
||||
"slug": "org56",
|
||||
"profile_image": "/uploads/organization/profile_image/934/ba061551-7cbe-4adc-80d0-bc60abcdaa32.png",
|
||||
"profile_image_90": "/uploads/organization/profile_image/934/ba061551-7cbe-4adc-80d0-bc60abcdaa32.png"
|
||||
},
|
||||
"flare_tag": {
|
||||
"name": "discuss",
|
||||
|
|
@ -162,6 +162,153 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/articles/{id}/unpublish": {
|
||||
"put": {
|
||||
"summary": "Unpublish an article",
|
||||
"tags": ["articles"],
|
||||
"description": "This endpoint allows the client to unpublish an article.\n\nThe user associated with the API key must have any 'admin' or 'moderator' role.\n\nThe article will be unpublished and will no longer be visible to the public. It will remain\nin the database and will set back to draft status on the author's posts dashboard. Any\nnotifications associated with the article will be deleted. Any comments on the article\nwill remain.",
|
||||
"operationId": "unpublishArticle",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The ID of the article to unpublish.",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"minimum": 1
|
||||
},
|
||||
"example": 1
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Article successfully unpublished"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"error": "unauthorized",
|
||||
"status": 401
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Article Not Found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"error": "not found",
|
||||
"status": 404
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/users/{id}/unpublish": {
|
||||
"put": {
|
||||
"summary": "Unpublish a User's Articles and Comments",
|
||||
"tags": ["users"],
|
||||
"description": "This endpoint allows the client to unpublish all of the articles and\ncomments created by a user.\n\nThe user associated with the API key must have any 'admin' or 'moderator' role.\n\nThis specified user's articles and comments will be unpublished and will no longer be\nvisible to the public. They will remain in the database and will set back to draft status\non the specified user's dashboard. Any notifications associated with the specified user's\narticles and comments will be deleted.\n\nNote this endpoint unpublishes articles and comments asychronously: it will return a 204 NO CONTENT\nstatus code immediately, but the articles and comments will not be unpublished until the\nrequest is completed on the server.",
|
||||
"operationId": "unpublishUser",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The ID of the user to unpublish.",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"minimum": 1
|
||||
},
|
||||
"example": 1
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "User's articles and comments successfully unpublished"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"error": "unauthorized",
|
||||
"status": 401
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Unknown User ID (still accepted for async processing)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"error": "not found",
|
||||
"status": 404
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/users/{id}/suspend": {
|
||||
"put": {
|
||||
"summary": "Suspend a User",
|
||||
"tags": ["users"],
|
||||
"description": "This endpoint allows the client to suspend a user.\n\nThe user associated with the API key must have any 'admin' or 'moderator' role.\n\nThis specified user will be assigned the 'suspended' role. Suspending a user will stop the\nuser from posting new posts and comments. It doesn't delete any of the user's content, just\nprevents them from creating new content while suspended. Users are not notified of their suspension\nin the UI, so if you want them to know about this, you must notify them.",
|
||||
"operationId": "suspendUser",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"description": "The ID of the user to suspend.",
|
||||
"schema": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"minimum": 1
|
||||
},
|
||||
"example": 1
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "User successfully unpublished"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"error": "unauthorized",
|
||||
"status": 401
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Unknown User ID",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"example": {
|
||||
"error": "not found",
|
||||
"status": 404
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"servers": [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue