From 78b42c5efd83e90ecbc5794638a5065045a64c50 Mon Sep 17 00:00:00 2001 From: Philip How Date: Mon, 18 Dec 2023 18:38:05 +0000 Subject: [PATCH] Add trusted role via API (#20455) * rubocop * use has_trusted_role? in tests, not trusted? due to pesky caching issues * swagger --- .../api/v1/user_roles_controller.rb | 12 +- config/routes.rb | 4 +- spec/requests/api/v1/docs/user_roles_spec.rb | 106 + spec/requests/api/v1/user_roles_spec.rb | 106 + swagger/v1/api_v1.json | 2248 ++++++++++++++--- 5 files changed, 2091 insertions(+), 385 deletions(-) diff --git a/app/controllers/api/v1/user_roles_controller.rb b/app/controllers/api/v1/user_roles_controller.rb index ccb919f89..f3a46ac50 100644 --- a/app/controllers/api/v1/user_roles_controller.rb +++ b/app/controllers/api/v1/user_roles_controller.rb @@ -5,7 +5,7 @@ module Api # 'suspended' is also known as 'suspend' for historical reasons SUSPEND_MODE = %w[suspend suspended].freeze - ROLES = (SUSPEND_MODE + %w[limited spam]).freeze + ROLES = (SUSPEND_MODE + %w[limited spam trusted]).freeze before_action :check_role before_action :set_target_user @@ -20,16 +20,16 @@ module Api add_role_to_target_user end - render json: { success: "okay" }, status: :no_content + render json: { success: "okay" }, status: :no_content # rubocop:disable Rails/UnusedRenderContent - adding this as it's been part of the API for a while end def destroy - # This mechanism for removing roles is pretty specific to limited, suspended and spam, - # where they revert to "Good standing" — we would need a different approach, - # possibly a whole different service object — to remove *any* roles + # This mechanism for removing roles is specific to limited, suspended, spam and trusted. + # We revert them to "Good standing". We would need a different approach, + # (possibly a whole different service object) to remove *any* roles remove_role_from_target_user - render json: { success: "okay" }, status: :no_content + render json: { success: "okay" }, status: :no_content # rubocop:disable Rails/UnusedRenderContent - adding this as it's been part of the API for a while end private diff --git a/config/routes.rb b/config/routes.rb index 78caf5a0b..79dac812a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -73,11 +73,11 @@ Rails.application.routes.draw do resources :organizations, only: %i[index create update destroy] scope("/users/:id") do - constraints(role: /suspend|suspended|limited|spam/) do + constraints(role: /suspend|suspended|limited|spam|trusted/) do put "/:role", to: "user_roles#update", as: "user_add_role" end - constraints(role: /limited|spam/) do + constraints(role: /limited|spam|trusted/) do delete "/:role", to: "user_roles#destroy", as: "user_remove_role" end end diff --git a/spec/requests/api/v1/docs/user_roles_spec.rb b/spec/requests/api/v1/docs/user_roles_spec.rb index 0c5c1148c..c181b9e6f 100644 --- a/spec/requests/api/v1/docs/user_roles_spec.rb +++ b/spec/requests/api/v1/docs/user_roles_spec.rb @@ -293,6 +293,112 @@ notify them." end end end + + describe "PUT /users/:id/trusted" do + before do + user.add_role(:admin) + end + + path "/api/users/{id}/trusted" do + put "Add trusted role for a User" do + tags "users" + description "This endpoint allows the client to add the trusted role to a user. + The user associated with the API key must have an 'admin' or 'moderator' role. + The specified user will be assigned the 'trusted' role. Adding the trusted role to a user + allows them to upvote and downvote posts and flag content that needs investigating by admins. + Users are notified of this change in the UI, and by email." + operationId "trustUser" + produces "application/json" + parameter name: :id, in: :path, required: true, + description: "The ID of the user to assign the trusted role.", + schema: { + type: :integer, + format: :int32, + minimum: 1 + }, + example: 1 + + response "204", "Trusted role assigned to the user successfully" do + let(:"api-key") { api_secret.secret } + let(:id) { 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) { 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 + + describe "DELETE /users/:id/trusted" do + before do + user.add_role(:admin) + end + + path "/api/users/{id}/trusted" do + delete "Remove trusted role from a User" do + tags "users" + description "This endpoint allows the client to remove the trusted role for a user. + The user associated with the API key must have an 'admin' or 'moderator' role. + The specified user will be restored to 'general' status. Users are not notified + of removing their trusted role in the UI, so if you want them to know about this, you must + notify them." + operationId "unTrustUser" + produces "application/json" + parameter name: :id, in: :path, required: true, + description: "The ID of the user to remove the trusted role from.", + schema: { + type: :integer, + format: :int32, + minimum: 1 + }, + example: 1 + + response "204", "Successfully removed the trusted role from a user" do + let(:"api-key") { api_secret.secret } + let(:id) { 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) { 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 end # rubocop:enable RSpec/VariableName diff --git a/spec/requests/api/v1/user_roles_spec.rb b/spec/requests/api/v1/user_roles_spec.rb index b21de0889..8933771f3 100644 --- a/spec/requests/api/v1/user_roles_spec.rb +++ b/spec/requests/api/v1/user_roles_spec.rb @@ -287,4 +287,110 @@ RSpec.describe "Api::V1::UserRoles" do end end end + + describe "PUT /api/users/:id/trusted", :aggregate_failures do + let(:target_user) { create(:user) } + + before { Audit::Subscribe.listen listener } + after { Audit::Subscribe.forget listener } + + context "when unauthenticated" do + it "returns unauthorized" do + put api_user_add_role_path(target_user, "trusted"), headers: headers + expect(response).to have_http_status(:unauthorized) + end + end + + context "when unauthorized" do + it "returns unauthorized if api key is invalid" do + put api_user_add_role_path(target_user, "trusted"), + headers: headers.merge({ "api-key" => "invalid api key" }) + + expect(response).to have_http_status(:unauthorized) + end + + it "returns unauthorized if api key belongs to non-admin user" do + put api_user_add_role_path(target_user, "trusted"), headers: auth_headers + + expect(response).to have_http_status(:unauthorized) + end + end + + context "when request is authenticated" do + before { api_secret.user.add_role(:super_admin) } + + it "is successful in assigning the trusted role to a user", :aggregate_failures do + expect do + put api_user_add_role_path(target_user, "trusted"), headers: auth_headers + expect(response).to have_http_status(:no_content) + expect(target_user.reload.has_trusted_role?).to be true + expect(Note.last.content).to match(/username\d+ updated username\d+/) + end.to change(Note, :count).by(1) + end + + it "creates an audit log of the action taken" do + put api_user_add_role_path(target_user, "trusted"), headers: auth_headers + + log = AuditLog.last + expect(log.category).to eq(AuditLog::ADMIN_API_AUDIT_LOG_CATEGORY) + expect(log.data["action"]).to eq("api_user_trusted") + expect(log.data["target_user_id"]).to eq(target_user.id) + expect(log.user_id).to eq(api_secret.user.id) + end + end + end + + describe "DELETE /api/users/:id/trusted", :aggregate_failures do + let(:target_user) { create(:user, :trusted) } + + before { Audit::Subscribe.listen listener } + after { Audit::Subscribe.forget listener } + + context "when unauthenticated" do + it "returns unauthorized" do + delete api_user_remove_role_path(target_user, "trusted"), headers: headers + expect(response).to have_http_status(:unauthorized) + end + end + + context "when unauthorized" do + it "returns unauthorized if api key is invalid" do + delete api_user_remove_role_path(target_user, "trusted"), + headers: headers.merge({ "api-key" => "invalid api key" }) + + expect(response).to have_http_status(:unauthorized) + end + + it "returns unauthorized if api key belongs to non-admin user" do + delete api_user_remove_role_path(target_user, "trusted"), headers: auth_headers + + expect(response).to have_http_status(:unauthorized) + end + end + + context "when request is authenticated" do + before { api_secret.user.add_role(:super_admin) } + + it "is successful in removing the trusted role from a user", :aggregate_failures do + expect do + delete api_user_remove_role_path(target_user, "trusted"), headers: auth_headers + + expect(response).to have_http_status(:no_content) + expect(target_user.reload.has_trusted_role?).to be false + expect(target_user.roles).to eq([]) + expect(Note.last.content).to match(/username\d+ updated username\d+/) + end.to change(Note, :count).by(1) + end + + it "creates an audit log of the action taken" do + delete api_user_remove_role_path(target_user, "trusted"), headers: auth_headers + + log = AuditLog.last + expect(log.category).to eq(AuditLog::ADMIN_API_AUDIT_LOG_CATEGORY) + expect(log.data["action"]).to eq("api_user_remove_trusted") + expect(log.data["target_user_id"]).to eq(target_user.id) + expect(log.user_id).to eq(api_secret.user.id) + end + end + end end diff --git a/swagger/v1/api_v1.json b/swagger/v1/api_v1.json index bb87ea567..d92e7ceb2 100644 --- a/swagger/v1/api_v1.json +++ b/swagger/v1/api_v1.json @@ -9,23 +9,77 @@ "/api/articles": { "post": { "summary": "Publish article", - "tags": [ - "articles" - ], + "tags": ["articles"], "description": "This endpoint allows the client to create a new article.\n\n\"Articles\" are all the posts that users create on DEV that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but is referred to as article within the code.", "operationId": "createArticle", - "parameters": [ - - ], + "parameters": [], "responses": { "201": { - "description": "An Article" + "description": "An Article", + "content": { + "application/json": { + "example": { + "type_of": "article", + "id": 2, + "title": "New article", + "description": "New post example", + "readable_publish_date": "Dec 15", + "slug": "new-article-2io1", + "path": "/username2/new-article-2io1", + "url": "http://forem.test/username2/new-article-2io1", + "comments_count": 0, + "public_reactions_count": 0, + "collection_id": 1, + "published_timestamp": "2023-12-15T00:15:27Z", + "positive_reactions_count": 0, + "cover_image": "https://thepracticaldev.s3.amazonaws.com/i/5wfo25724gzgk5e5j50g.jpg", + "social_image": "https://thepracticaldev.s3.amazonaws.com/i/5wfo25724gzgk5e5j50g.jpg", + "canonical_url": "https://dev.to/fdocr/headless-chrome-dual-mode-tests-for-ruby-on-rails-4p6g", + "created_at": "2023-12-15T00:15:27Z", + "edited_at": null, + "crossposted_at": null, + "published_at": "2023-12-15T00:15:27Z", + "last_comment_at": "2023-12-15T00:15:27Z", + "reading_time_minutes": 1, + "tag_list": "", + "tags": [], + "body_html": "

New body for the article

\n\n", + "body_markdown": "**New** body for the article", + "user": { + "name": "Carmen \"Andre\" \\:/ Rodriguez", + "username": "username2", + "twitter_username": "twitter2", + "github_username": "github2", + "user_id": 882, + "website_url": null, + "profile_image": "/uploads/user/profile_image/882/a6c7c9f0-6c40-491e-a306-a03c926c6aac.jpeg", + "profile_image_90": "/uploads/user/profile_image/882/a6c7c9f0-6c40-491e-a306-a03c926c6aac.jpeg" + } + } + } + } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "422": { - "description": "Unprocessable Entity" + "description": "Unprocessable Entity", + "content": { + "application/json": { + "example": { + "error": "param is missing or the value is empty: article", + "status": 422 + } + } + } } }, "requestBody": { @@ -40,12 +94,8 @@ }, "get": { "summary": "Published articles", - "security": [ - - ], - "tags": [ - "articles" - ], + "security": [], + "tags": ["articles"], "description": "This endpoint allows the client to retrieve a list of articles.\n\n\"Articles\" are all the posts that users create on DEV that typically\nshow up in the feed. They can be a blog post, a discussion question,\na help thread etc. but is referred to as article within the code.\n\nBy default it will return featured, published articles ordered\nby descending popularity.\n\nIt supports pagination, each page will contain `30` articles by default.", "operationId": "getArticles", "parameters": [ @@ -102,11 +152,7 @@ "description": "Using this parameter will allow the client to check which articles are fresh or rising.\n If `state=fresh` the server will return fresh articles.\n If `state=rising` the server will return rising articles.\n This param can be used in conjuction with `username`, only if set to `all`.", "schema": { "type": "string", - "enum": [ - "fresh", - "rising", - "all" - ] + "enum": ["fresh", "rising", "all"] }, "example": "fresh" }, @@ -139,6 +185,56 @@ "description": "A List of Articles", "content": { "application/json": { + "example": [ + { + "type_of": "article", + "id": 5, + "title": "Let Us Now Praise Famous Men4", + "description": "Street yr raw denim master. Twee vice selvage neutra. Single-origin coffee crucifix franzen lo-fi...", + "readable_publish_date": "Dec 15", + "slug": "let-us-now-praise-famous-men4-1oj7", + "path": "/username6/let-us-now-praise-famous-men4-1oj7", + "url": "http://forem.test/username6/let-us-now-praise-famous-men4-1oj7", + "comments_count": 0, + "public_reactions_count": 0, + "collection_id": null, + "published_timestamp": "2023-12-15T00:15:28Z", + "positive_reactions_count": 0, + "cover_image": "http://forem.test/assets/8-915172672c34364d29c3fce07afa413c1ac072beff54ddd79fc7e3ed633556a1.png", + "social_image": "http://forem.test/assets/8-915172672c34364d29c3fce07afa413c1ac072beff54ddd79fc7e3ed633556a1.png", + "canonical_url": "http://forem.test/username6/let-us-now-praise-famous-men4-1oj7", + "created_at": "2023-12-15T00:15:28Z", + "edited_at": null, + "crossposted_at": null, + "published_at": "2023-12-15T00:15:28Z", + "last_comment_at": "2023-12-15T00:15:28Z", + "reading_time_minutes": 1, + "tag_list": ["discuss"], + "tags": "discuss", + "user": { + "name": "Lanette \"Marine\" \\:/ Stanton", + "username": "username6", + "twitter_username": "twitter6", + "github_username": "github6", + "user_id": 886, + "website_url": null, + "profile_image": "/uploads/user/profile_image/886/e13bee17-64f8-43d8-9cdb-ba17c28cbdb4.jpeg", + "profile_image_90": "/uploads/user/profile_image/886/e13bee17-64f8-43d8-9cdb-ba17c28cbdb4.jpeg" + }, + "organization": { + "name": "Spinka and Sons", + "username": "org4", + "slug": "org4", + "profile_image": "/uploads/organization/profile_image/4/f1003659-a58c-4019-89d1-24817528626a.png", + "profile_image_90": "/uploads/organization/profile_image/4/f1003659-a58c-4019-89d1-24817528626a.png" + }, + "flare_tag": { + "name": "discuss", + "bg_color_hex": "#000000", + "text_color_hex": "#ffffff" + } + } + ], "schema": { "type": "array", "items": { @@ -154,12 +250,8 @@ "/api/articles/latest": { "get": { "summary": "Published articles sorted by published date", - "security": [ - - ], - "tags": [ - "articles" - ], + "security": [], + "tags": ["articles"], "description": "This endpoint allows the client to retrieve a list of articles. ordered by descending publish date.\n\nIt supports pagination, each page will contain 30 articles by default.", "operationId": "getLatestArticles", "parameters": [ @@ -175,6 +267,131 @@ "description": "A List of Articles", "content": { "application/json": { + "example": [ + { + "type_of": "article", + "id": 8, + "title": "Precious Bane7", + "description": "Etsy green juice trust fund irony sriracha bushwick flexitarian pickled. Blog kitsch...", + "readable_publish_date": "Dec 15", + "slug": "precious-bane7-31i9", + "path": "/username9/precious-bane7-31i9", + "url": "http://forem.test/username9/precious-bane7-31i9", + "comments_count": 0, + "public_reactions_count": 0, + "collection_id": null, + "published_timestamp": "2023-12-15T00:15:28Z", + "positive_reactions_count": 0, + "cover_image": "http://forem.test/assets/29-62fdba2773105cf85b89a795a479be680d13a73e6b5406cacaa2458d403dda8c.png", + "social_image": "http://forem.test/assets/29-62fdba2773105cf85b89a795a479be680d13a73e6b5406cacaa2458d403dda8c.png", + "canonical_url": "http://forem.test/username9/precious-bane7-31i9", + "created_at": "2023-12-15T00:15:28Z", + "edited_at": null, + "crossposted_at": null, + "published_at": "2023-12-15T00:15:28Z", + "last_comment_at": "2023-12-15T00:15:28Z", + "reading_time_minutes": 1, + "tag_list": ["javascript", "html", "discuss"], + "tags": "javascript, html, discuss", + "user": { + "name": "Bobette \"Ling\" \\:/ Kuvalis", + "username": "username9", + "twitter_username": "twitter9", + "github_username": "github9", + "user_id": 889, + "website_url": null, + "profile_image": "/uploads/user/profile_image/889/114ac38d-7b97-41d5-9668-c44b27fa8fa1.jpeg", + "profile_image_90": "/uploads/user/profile_image/889/114ac38d-7b97-41d5-9668-c44b27fa8fa1.jpeg" + }, + "flare_tag": { + "name": "discuss", + "bg_color_hex": "#000000", + "text_color_hex": "#ffffff" + } + }, + { + "type_of": "article", + "id": 7, + "title": "Cover Her Face6", + "description": "Park aesthetic tattooed godard post-ironic wolf beard cold-pressed. Tumblr authentic roof readymade...", + "readable_publish_date": "Dec 15", + "slug": "cover-her-face6-12p3", + "path": "/username8/cover-her-face6-12p3", + "url": "http://forem.test/username8/cover-her-face6-12p3", + "comments_count": 0, + "public_reactions_count": 0, + "collection_id": null, + "published_timestamp": "2023-12-15T00:15:28Z", + "positive_reactions_count": 0, + "cover_image": "http://forem.test/assets/25-b4bb206b62bee552880440f638594e41dcd649ed9bd821af2e8dfc671d1d6813.png", + "social_image": "http://forem.test/assets/25-b4bb206b62bee552880440f638594e41dcd649ed9bd821af2e8dfc671d1d6813.png", + "canonical_url": "http://forem.test/username8/cover-her-face6-12p3", + "created_at": "2023-12-15T00:15:28Z", + "edited_at": null, + "crossposted_at": null, + "published_at": "2023-12-15T00:15:28Z", + "last_comment_at": "2023-12-15T00:15:28Z", + "reading_time_minutes": 1, + "tag_list": ["javascript", "html", "discuss"], + "tags": "javascript, html, discuss", + "user": { + "name": "Karen \"Nakia\" \\:/ Lockman", + "username": "username8", + "twitter_username": "twitter8", + "github_username": "github8", + "user_id": 888, + "website_url": null, + "profile_image": "/uploads/user/profile_image/888/bb690b36-2309-455f-81f4-98b4ec247427.jpeg", + "profile_image_90": "/uploads/user/profile_image/888/bb690b36-2309-455f-81f4-98b4ec247427.jpeg" + }, + "flare_tag": { + "name": "discuss", + "bg_color_hex": "#000000", + "text_color_hex": "#ffffff" + } + }, + { + "type_of": "article", + "id": 6, + "title": "I Know Why the Caged Bird Sings5", + "description": "Carry meh farm-to-table. Fanny pack before they sold out normcore fingerstache disrupt church-key...", + "readable_publish_date": "Dec 15", + "slug": "i-know-why-the-caged-bird-sings5-393f", + "path": "/username7/i-know-why-the-caged-bird-sings5-393f", + "url": "http://forem.test/username7/i-know-why-the-caged-bird-sings5-393f", + "comments_count": 0, + "public_reactions_count": 0, + "collection_id": null, + "published_timestamp": "2023-12-15T00:15:28Z", + "positive_reactions_count": 0, + "cover_image": "http://forem.test/assets/7-7dc75c1a59875db65e2539f321090f6cb232c3dbffdbe4367b0d32b8f2797758.png", + "social_image": "http://forem.test/assets/7-7dc75c1a59875db65e2539f321090f6cb232c3dbffdbe4367b0d32b8f2797758.png", + "canonical_url": "http://forem.test/username7/i-know-why-the-caged-bird-sings5-393f", + "created_at": "2023-12-15T00:15:28Z", + "edited_at": null, + "crossposted_at": null, + "published_at": "2023-12-15T00:15:28Z", + "last_comment_at": "2023-12-15T00:15:28Z", + "reading_time_minutes": 1, + "tag_list": ["javascript", "html", "discuss"], + "tags": "javascript, html, discuss", + "user": { + "name": "Darby \"Damian\" \\:/ Quitzon", + "username": "username7", + "twitter_username": "twitter7", + "github_username": "github7", + "user_id": 887, + "website_url": null, + "profile_image": "/uploads/user/profile_image/887/9e09ae0d-d45e-46e6-bcab-3b65ffef6c4d.jpeg", + "profile_image_90": "/uploads/user/profile_image/887/9e09ae0d-d45e-46e6-bcab-3b65ffef6c4d.jpeg" + }, + "flare_tag": { + "name": "discuss", + "bg_color_hex": "#000000", + "text_color_hex": "#ffffff" + } + } + ], "schema": { "type": "array", "items": { @@ -190,12 +407,8 @@ "/api/articles/{id}": { "get": { "summary": "Published article by id", - "security": [ - - ], - "tags": [ - "articles" - ], + "security": [], + "tags": ["articles"], "description": "This endpoint allows the client to retrieve a single published article given its `id`.", "operationId": "getArticleById", "parameters": [ @@ -213,6 +426,49 @@ "description": "An Article", "content": { "application/json": { + "example": { + "type_of": "article", + "id": 9, + "title": "It's a Battlefield8", + "description": "Venmo slow-carb vhs ugh cronut crucifix. Freegan hella hammock helvetica you probably haven't heard...", + "readable_publish_date": "Dec 15", + "slug": "its-a-battlefield8-2af", + "path": "/username10/its-a-battlefield8-2af", + "url": "http://forem.test/username10/its-a-battlefield8-2af", + "comments_count": 0, + "public_reactions_count": 0, + "collection_id": null, + "published_timestamp": "2023-12-15T00:15:28Z", + "positive_reactions_count": 0, + "cover_image": "http://forem.test/assets/14-5e64731cc7cd63e3b689647d9d3c3e4e1d907690c716d3dd1e356466726a2c2d.png", + "social_image": "http://forem.test/assets/14-5e64731cc7cd63e3b689647d9d3c3e4e1d907690c716d3dd1e356466726a2c2d.png", + "canonical_url": "http://forem.test/username10/its-a-battlefield8-2af", + "created_at": "2023-12-15T00:15:28Z", + "edited_at": null, + "crossposted_at": null, + "published_at": "2023-12-15T00:15:28Z", + "last_comment_at": "2023-12-15T00:15:28Z", + "reading_time_minutes": 1, + "tag_list": "discuss", + "tags": ["discuss"], + "body_html": "

Venmo slow-carb vhs ugh cronut crucifix. Freegan hella hammock helvetica you probably haven't heard of them try-hard. Humblebrag iphone fashion axe small batch flexitarian fixie.

\n\n

Neutra cliche occupy trust fund actually. Heirloom vinyl vice freegan. Pbr&b whatever organic dreamcatcher kickstarter pork belly authentic.

\n\n", + "body_markdown": "---\ntitle: It's a Battlefield8\npublished: true\ntags: discuss\ndate: \nseries: \ncanonical_url: \n\n---\n\nVenmo slow-carb vhs ugh cronut crucifix. Freegan hella hammock helvetica you probably haven't heard of them try-hard. Humblebrag iphone fashion axe small batch flexitarian fixie.\n\n\nNeutra cliche occupy trust fund actually. Heirloom vinyl vice freegan. Pbr&b whatever organic dreamcatcher kickstarter pork belly authentic.\n\n", + "user": { + "name": "Raylene \"Romana\" \\:/ Bailey", + "username": "username10", + "twitter_username": "twitter10", + "github_username": "github10", + "user_id": 890, + "website_url": null, + "profile_image": "/uploads/user/profile_image/890/f6fe914d-9deb-461c-bb67-2f8af4957a89.jpeg", + "profile_image_90": "/uploads/user/profile_image/890/f6fe914d-9deb-461c-bb67-2f8af4957a89.jpeg" + }, + "flare_tag": { + "name": "discuss", + "bg_color_hex": "#000000", + "text_color_hex": "#ffffff" + } + }, "schema": { "type": "object", "items": { @@ -223,15 +479,21 @@ } }, "404": { - "description": "Article Not Found" + "description": "Article Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } }, "put": { "summary": "Update an article by id", - "tags": [ - "articles" - ], + "tags": ["articles"], "description": "This endpoint allows the client to update an existing article.\n\n\"Articles\" are all the posts that users create on DEV that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but is referred to as article within the code.", "operationId": "updateArticle", "parameters": [ @@ -250,16 +512,82 @@ ], "responses": { "200": { - "description": "An Article" + "description": "An Article", + "content": { + "application/json": { + "example": { + "type_of": "article", + "id": 10, + "title": "Recalled to Life9", + "description": "Messenger bag kickstarter fingerstache. Green juice hammock taxidermy. Gastropub pop-up godard put a...", + "readable_publish_date": "Dec 15", + "slug": "recalled-to-life9-1990", + "path": "/username11/recalled-to-life9-1990", + "url": "http://forem.test/username11/recalled-to-life9-1990", + "comments_count": 0, + "public_reactions_count": 0, + "collection_id": null, + "published_timestamp": "2023-12-15T00:15:28Z", + "positive_reactions_count": 0, + "cover_image": "http://forem.test/assets/21-8c16e6ef44da175a1e51f1ba9d0cb55af8a920db6aacbf1e4b7a055afc1b3d30.png", + "social_image": "http://forem.test/assets/21-8c16e6ef44da175a1e51f1ba9d0cb55af8a920db6aacbf1e4b7a055afc1b3d30.png", + "canonical_url": "http://forem.test/username11/recalled-to-life9-1990", + "created_at": "2023-12-15T00:15:28Z", + "edited_at": "2023-12-15T00:15:28Z", + "crossposted_at": null, + "published_at": "2023-12-15T00:15:28Z", + "last_comment_at": "2023-12-15T00:15:28Z", + "reading_time_minutes": 1, + "tag_list": "", + "tags": [], + "body_html": "

New body for the article

\n\n", + "body_markdown": "**New** body for the article", + "user": { + "name": "Danilo \"Morton\" \\:/ Douglas", + "username": "username11", + "twitter_username": "twitter11", + "github_username": "github11", + "user_id": 891, + "website_url": null, + "profile_image": "/uploads/user/profile_image/891/e23f88f8-724f-4e6d-b6e1-1b3a9f04f39b.jpeg", + "profile_image_90": "/uploads/user/profile_image/891/e23f88f8-724f-4e6d-b6e1-1b3a9f04f39b.jpeg" + } + } + } + } }, "404": { - "description": "Article Not Found" + "description": "Article Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "422": { - "description": "Unprocessable Entity" + "description": "Unprocessable Entity", + "content": { + "application/json": { + "example": { + "error": "param is missing or the value is empty: article", + "status": 422 + } + } + } } }, "requestBody": { @@ -276,12 +604,8 @@ "/api/articles/{username}/{slug}": { "get": { "summary": "Published article by path", - "security": [ - - ], - "tags": [ - "articles" - ], + "security": [], + "tags": ["articles"], "description": "This endpoint allows the client to retrieve a single published article given its `path`.", "operationId": "getArticleByPath", "parameters": [ @@ -307,6 +631,49 @@ "description": "An Article", "content": { "application/json": { + "example": { + "type_of": "article", + "id": 13, + "title": "Far From the Madding Crowd12", + "description": "3 wolf moon asymmetrical mumblecore put a bird on it tilde yolo mustache crucifix. Heirloom scenester...", + "readable_publish_date": "Dec 15", + "slug": "far-from-the-madding-crowd12-1m5d", + "path": "/username15/far-from-the-madding-crowd12-1m5d", + "url": "http://forem.test/username15/far-from-the-madding-crowd12-1m5d", + "comments_count": 0, + "public_reactions_count": 0, + "collection_id": null, + "published_timestamp": "2023-12-15T00:15:29Z", + "positive_reactions_count": 0, + "cover_image": "http://forem.test/assets/2-1a96ae446ded018b65b215cce3aecc40a00e701642da521fdd6edd3c593ff6c1.png", + "social_image": "http://forem.test/assets/2-1a96ae446ded018b65b215cce3aecc40a00e701642da521fdd6edd3c593ff6c1.png", + "canonical_url": "http://forem.test/username15/far-from-the-madding-crowd12-1m5d", + "created_at": "2023-12-15T00:15:29Z", + "edited_at": null, + "crossposted_at": null, + "published_at": "2023-12-15T00:15:29Z", + "last_comment_at": "2023-12-15T00:15:29Z", + "reading_time_minutes": 1, + "tag_list": "discuss", + "tags": ["discuss"], + "body_html": "

3 wolf moon asymmetrical mumblecore put a bird on it tilde yolo mustache crucifix. Heirloom scenester crucifix pickled skateboard. Synth 8-bit flexitarian typewriter banjo. Crucifix synth wayfarers humblebrag locavore vice brooklyn organic.

\n\n

Microdosing cronut muggle magic authentic carry. Occupy kombucha synth celiac food truck you probably haven't heard of them.

\n\n", + "body_markdown": "---\ntitle: Far From the Madding Crowd12\npublished: true\ntags: discuss\ndate: \nseries: \ncanonical_url: \n\n---\n\n3 wolf moon asymmetrical mumblecore put a bird on it tilde yolo mustache crucifix. Heirloom scenester crucifix pickled skateboard. Synth 8-bit flexitarian typewriter banjo. Crucifix synth wayfarers humblebrag locavore vice brooklyn organic.\n\n\nMicrodosing cronut muggle magic authentic carry. Occupy kombucha synth celiac food truck you probably haven't heard of them.\n\n", + "user": { + "name": "Noreen \"Alvaro\" \\:/ Schinner", + "username": "username15", + "twitter_username": "twitter15", + "github_username": "github15", + "user_id": 895, + "website_url": null, + "profile_image": "/uploads/user/profile_image/895/9e4706ef-d56d-4167-9355-1a3db85801e1.jpeg", + "profile_image_90": "/uploads/user/profile_image/895/9e4706ef-d56d-4167-9355-1a3db85801e1.jpeg" + }, + "flare_tag": { + "name": "discuss", + "bg_color_hex": "#000000", + "text_color_hex": "#ffffff" + } + }, "schema": { "type": "object", "items": { @@ -317,7 +684,15 @@ } }, "404": { - "description": "Article Not Found" + "description": "Article Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -325,10 +700,7 @@ "/api/articles/me": { "get": { "summary": "User's articles", - "tags": [ - "articles", - "users" - ], + "tags": ["articles", "users"], "description": "This endpoint allows the client to retrieve a list of published articles on behalf of an authenticated user.\n\n\"Articles\" are all the posts that users create on DEV that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but is referred to as article within the code.\n\nPublished articles will be in reverse chronological publication order.\n\nIt will return published articles with pagination. By default a page will contain 30 articles.", "operationId": "getUserArticles", "parameters": [ @@ -341,12 +713,21 @@ ], "responses": { "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "200": { "description": "A List of the authenticated user's Articles", "content": { "application/json": { + "example": [], "schema": { "type": "array", "items": { @@ -362,10 +743,7 @@ "/api/articles/me/published": { "get": { "summary": "User's published articles", - "tags": [ - "articles", - "users" - ], + "tags": ["articles", "users"], "description": "This endpoint allows the client to retrieve a list of published articles on behalf of an authenticated user.\n\n\"Articles\" are all the posts that users create on DEV that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but is referred to as article within the code.\n\nPublished articles will be in reverse chronological publication order.\n\nIt will return published articles with pagination. By default a page will contain 30 articles.", "operationId": "getUserPublishedArticles", "parameters": [ @@ -378,12 +756,21 @@ ], "responses": { "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "200": { "description": "A List of the authenticated user's Articles", "content": { "application/json": { + "example": [], "schema": { "type": "array", "items": { @@ -399,10 +786,7 @@ "/api/articles/me/unpublished": { "get": { "summary": "User's unpublished articles", - "tags": [ - "articles", - "users" - ], + "tags": ["articles", "users"], "description": "This endpoint allows the client to retrieve a list of unpublished articles on behalf of an authenticated user.\n\n\"Articles\" are all the posts that users create on DEV that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but is referred to as article within the code.\n\nUnpublished articles will be in reverse chronological creation order.\n\nIt will return unpublished articles with pagination. By default a page will contain 30 articles.", "operationId": "getUserUnpublishedArticles", "parameters": [ @@ -415,12 +799,21 @@ ], "responses": { "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "200": { "description": "A List of the authenticated user's Articles", "content": { "application/json": { + "example": [], "schema": { "type": "array", "items": { @@ -436,10 +829,7 @@ "/api/articles/me/all": { "get": { "summary": "User's all articles", - "tags": [ - "articles", - "users" - ], + "tags": ["articles", "users"], "description": "This endpoint allows the client to retrieve a list of all articles on behalf of an authenticated user.\n\n\"Articles\" are all the posts that users create on DEV that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but is referred to as article within the code.\n\nIt will return both published and unpublished articles with pagination.\n\nUnpublished articles will be at the top of the list in reverse chronological creation order. Published articles will follow in reverse chronological publication order.\n\nBy default a page will contain 30 articles.", "operationId": "getUserAllArticles", "parameters": [ @@ -452,12 +842,21 @@ ], "responses": { "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "200": { "description": "A List of the authenticated user's Articles", "content": { "application/json": { + "example": [], "schema": { "type": "array", "items": { @@ -473,9 +872,7 @@ "/api/articles/{id}/unpublish": { "put": { "summary": "Unpublish an article", - "tags": [ - "articles" - ], + "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": [ @@ -507,10 +904,26 @@ "description": "Article successfully unpublished" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Article Not Found" + "description": "Article Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -518,9 +931,7 @@ "/api/segments": { "get": { "summary": "Manually managed audience segments", - "tags": [ - "segments" - ], + "tags": ["segments"], "description": "This endpoint allows the client to retrieve a list of audience segments.\n\nAn audience segment is a group of users that can be targeted by a Billboard. This API only permits managing segments you create and maintain yourself.\n\nThe endpoint supports pagination, and each page will contain `30` segments by default.", "operationId": "getSegments", "parameters": [ @@ -533,6 +944,22 @@ "description": "A List of manually managed audience segments", "content": { "application/json": { + "example": [ + { + "id": 2, + "created_at": "2023-12-15T11:15:30.398+11:00", + "type_of": "manual", + "updated_at": "2023-12-15T11:15:30.398+11:00", + "user_count": 1 + }, + { + "id": 1, + "created_at": "2023-12-15T11:15:30.308+11:00", + "type_of": "manual", + "updated_at": "2023-12-15T11:15:30.308+11:00", + "user_count": 3 + } + ], "schema": { "type": "array", "items": { @@ -543,23 +970,47 @@ } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } } } }, "post": { "summary": "Create a manually managed audience segment", - "tags": [ - "segments" - ], + "tags": ["segments"], "description": "This endpoint allows the client to create a new audience segment.\n\nAn audience segment is a group of users that can be targeted by a Billboard. This API only permits managing segments you create and maintain yourself.", "operationId": "createSegment", "responses": { "201": { - "description": "A manually managed audience segment" + "description": "A manually managed audience segment", + "content": { + "application/json": { + "example": { + "id": 3, + "created_at": "2023-12-15T11:15:30.723+11:00", + "type_of": "manual", + "updated_at": "2023-12-15T11:15:30.723+11:00" + } + } + } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } } } } @@ -567,9 +1018,7 @@ "/api/segments/{id}": { "get": { "summary": "A manually managed audience segment", - "tags": [ - "segments" - ], + "tags": ["segments"], "description": "This endpoint allows the client to retrieve a single manually-managed audience segment specified by ID.", "operationId": "getSegment", "parameters": [ @@ -589,6 +1038,13 @@ "description": "The audience segment", "content": { "application/json": { + "example": { + "id": 4, + "created_at": "2023-12-15T11:15:30.937+11:00", + "type_of": "manual", + "updated_at": "2023-12-15T11:15:30.937+11:00", + "user_count": 3 + }, "schema": { "type": "object", "items": { @@ -599,18 +1055,32 @@ } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Audience Segment Not Found" + "description": "Audience Segment Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } }, "delete": { "summary": "Delete a manually managed audience segment", - "tags": [ - "segments" - ], + "tags": ["segments"], "description": "This endpoint allows the client to delete an audience segment specified by ID.\n\nAudience segments cannot be deleted if there are still any Billboards using them.", "operationId": "deleteSegment", "parameters": [ @@ -627,16 +1097,49 @@ ], "responses": { "200": { - "description": "The deleted audience segment" + "description": "The deleted audience segment", + "content": { + "application/json": { + "example": { + "id": 8, + "created_at": "2023-12-15T11:15:31.351+11:00", + "type_of": "manual", + "updated_at": "2023-12-15T11:15:31.351+11:00" + } + } + } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Audience Segment Not Found" + "description": "Audience Segment Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } }, "409": { - "description": "Audience segment could not be deleted" + "description": "Audience segment could not be deleted", + "content": { + "application/json": { + "example": { + "error": "Segments cannot be deleted while in use by any billboards" + } + } + } } } } @@ -644,9 +1147,7 @@ "/api/segments/{id}/users": { "get": { "summary": "Users in a manually managed audience segment", - "tags": [ - "segments" - ], + "tags": ["segments"], "description": "This endpoint allows the client to retrieve a list of the users in an audience segment specified by ID. The endpoint supports pagination, and each page will contain `30` users by default.", "operationId": "getUsersInSegment", "parameters": [ @@ -669,6 +1170,47 @@ "description": "A List of users in the audience segment", "content": { "application/json": { + "example": [ + { + "type_of": "user", + "id": 935, + "username": "username55", + "name": "Douglas \"Ervin\" \\:/ Homenick", + "twitter_username": "twitter55", + "github_username": "github55", + "summary": null, + "location": null, + "website_url": null, + "joined_at": "Dec 15, 2023", + "profile_image": "/uploads/user/profile_image/935/f305ebc2-4b79-4bd2-8cf3-068dce1744f1.jpeg" + }, + { + "type_of": "user", + "id": 936, + "username": "username56", + "name": "Cheree \"Brandon\" \\:/ Kilback", + "twitter_username": "twitter56", + "github_username": "github56", + "summary": null, + "location": null, + "website_url": null, + "joined_at": "Dec 15, 2023", + "profile_image": "/uploads/user/profile_image/936/c39ca408-c6f0-4812-b69e-98ac37b03526.jpeg" + }, + { + "type_of": "user", + "id": 937, + "username": "username57", + "name": "Reta \"Janetta\" \\:/ Heathcote", + "twitter_username": "twitter57", + "github_username": "github57", + "summary": null, + "location": null, + "website_url": null, + "joined_at": "Dec 15, 2023", + "profile_image": "/uploads/user/profile_image/937/d2b7e4ec-9c26-401f-83dc-18852e2d5617.jpeg" + } + ], "schema": { "type": "array", "items": { @@ -679,10 +1221,26 @@ } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Audience Segment Not Found" + "description": "Audience Segment Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -690,9 +1248,7 @@ "/api/segments/{id}/add_users": { "put": { "summary": "Add users to a manually managed audience segment", - "tags": [ - "segments" - ], + "tags": ["segments"], "description": "This endpoint allows the client to add users in bulk to an audience segment specified by ID.\n\nSuccesses are users that were included in the segment (even if they were already in it), and failures are users that could not be added to the segment.", "operationId": "addUsersToSegment", "parameters": [ @@ -709,16 +1265,48 @@ ], "responses": { "200": { - "description": "Result of adding the users to the segment." + "description": "Result of adding the users to the segment.", + "content": { + "application/json": { + "example": { + "succeeded": [943, 944, 945], + "failed": [] + } + } + } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Audience Segment Not Found" + "description": "Audience Segment Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } }, "422": { - "description": "Unprocessable Entity" + "description": "Unprocessable Entity", + "content": { + "application/json": { + "example": { + "error": "param is missing or the value is empty: user_ids", + "status": 422 + } + } + } } }, "requestBody": { @@ -735,9 +1323,7 @@ "/api/segments/{id}/remove_users": { "put": { "summary": "Remove users from a manually managed audience segment", - "tags": [ - "segments" - ], + "tags": ["segments"], "description": "This endpoint allows the client to remove users in bulk from an audience segment specified by ID.\n\nSuccesses are users that were removed; failures are users that weren't a part of the segment.", "operationId": "removeUsersFromSegment", "parameters": [ @@ -754,16 +1340,48 @@ ], "responses": { "200": { - "description": "Result of removing the users to the segment." + "description": "Result of removing the users to the segment.", + "content": { + "application/json": { + "example": { + "succeeded": [962, 963, 964], + "failed": [] + } + } + } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Audience Segment Not Found" + "description": "Audience Segment Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } }, "422": { - "description": "Unprocessable Entity" + "description": "Unprocessable Entity", + "content": { + "application/json": { + "example": { + "error": "param is missing or the value is empty: user_ids", + "status": 422 + } + } + } } }, "requestBody": { @@ -780,15 +1398,14 @@ "/api/billboards": { "get": { "summary": "Billboards", - "tags": [ - "billboards" - ], + "tags": ["billboards"], "description": "This endpoint allows the client to retrieve a list of all billboards.", "responses": { "200": { "description": "successful", "content": { "application/json": { + "example": [], "schema": { "type": "array", "items": { @@ -799,24 +1416,57 @@ } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } } } }, "post": { "summary": "Create a billboard", - "tags": [ - "billboards" - ], + "tags": ["billboards"], "description": "This endpoint allows the client to create a new billboard.", - "parameters": [ - - ], + "parameters": [], "responses": { "201": { "description": "A billboard", "content": { "application/json": { + "example": { + "id": 2, + "approved": true, + "audience_segment_id": null, + "body_markdown": "# Hi, this is ad\nYep, it's an ad", + "cached_tag_list": "", + "clicks_count": 0, + "created_at": "2023-12-15T11:15:34.267+11:00", + "creator_id": null, + "custom_display_label": null, + "display_to": "all", + "exclude_article_ids": "", + "impressions_count": 0, + "name": "Example Billboard", + "organization_id": null, + "placement_area": "post_comments", + "priority": false, + "processed_html": "

Hi, this is ad

Yep, it's an ad

", + "published": true, + "render_mode": "forem_markdown", + "success_rate": 0.0, + "template": "authorship_box", + "type_of": "in_house", + "updated_at": "2023-12-15T11:15:34.267+11:00", + "weight": 1.0, + "audience_segment_type": null, + "tag_list": "", + "target_geolocations": ["US-WA", "CA-BC"] + }, "schema": { "type": "object", "items": { @@ -827,10 +1477,26 @@ } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "422": { - "description": "unprocessable" + "description": "unprocessable", + "content": { + "application/json": { + "example": { + "error": "Validation failed: Placement area is not included in the list", + "status": 422 + } + } + } } }, "requestBody": { @@ -850,9 +1516,7 @@ "/api/billboards/{id}": { "get": { "summary": "A billboard (by id)", - "tags": [ - "billboards" - ], + "tags": ["billboards"], "description": "This endpoint allows the client to retrieve a single billboard, via its id.", "parameters": [ { @@ -870,21 +1534,68 @@ ], "responses": { "200": { - "description": "successful" + "description": "successful", + "content": { + "application/json": { + "example": { + "id": 3, + "approved": false, + "audience_segment_id": null, + "body_markdown": "Hello _hey_ Hey hey 2", + "cached_tag_list": "", + "clicks_count": 0, + "created_at": "2023-12-15T11:15:34.492+11:00", + "creator_id": null, + "custom_display_label": null, + "display_to": "all", + "exclude_article_ids": "", + "impressions_count": 0, + "name": "Billboard 3", + "organization_id": 6, + "placement_area": "sidebar_left", + "priority": false, + "processed_html": "

Hello hey Hey hey 2

", + "published": false, + "render_mode": "forem_markdown", + "success_rate": 0.0, + "template": "authorship_box", + "type_of": "in_house", + "updated_at": "2023-12-15T11:15:34.495+11:00", + "weight": 1.0, + "audience_segment_type": null, + "tag_list": "", + "target_geolocations": [] + } + } + } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Unknown Billboard ID" + "description": "Unknown Billboard ID", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } }, "put": { "summary": "Update a billboard by ID", - "tags": [ - "billboards" - ], + "tags": ["billboards"], "description": "This endpoint allows the client to update the attributes of a single billboard, via its id.", "parameters": [ { @@ -905,6 +1616,35 @@ "description": "successful", "content": { "application/json": { + "example": { + "approved": false, + "body_markdown": "Hello _hey_ Hey hey 3", + "creator_id": null, + "display_to": "all", + "name": "Billboard 4", + "organization_id": 7, + "placement_area": "sidebar_left", + "published": false, + "type_of": "in_house", + "exclude_article_ids": "", + "weight": 1.0, + "audience_segment_id": null, + "priority": false, + "custom_display_label": null, + "template": "authorship_box", + "render_mode": "forem_markdown", + "cached_tag_list": "", + "id": 4, + "clicks_count": 0, + "created_at": "2023-12-15T11:15:34.735+11:00", + "impressions_count": 0, + "processed_html": "

Hello hey Hey hey 3

", + "success_rate": 0.0, + "updated_at": "2023-12-15T11:15:34.738+11:00", + "audience_segment_type": null, + "tag_list": "", + "target_geolocations": [] + }, "schema": { "type": "object", "items": { @@ -915,10 +1655,26 @@ } }, "404": { - "description": "not found" + "description": "not found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } } }, "requestBody": { @@ -938,9 +1694,7 @@ "/api/billboards/{id}/unpublish": { "put": { "summary": "Unpublish a billboard", - "tags": [ - "billboards" - ], + "tags": ["billboards"], "description": "This endpoint allows the client to remove a billboard from rotation by un-publishing it.", "parameters": [ { @@ -961,10 +1715,26 @@ "description": "no content" }, "404": { - "description": "not found" + "description": "not found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } } } } @@ -972,12 +1742,8 @@ "/api/comments": { "get": { "summary": "Comments", - "security": [ - - ], - "tags": [ - "comments" - ], + "security": [], + "tags": ["comments"], "description": "This endpoint allows the client to retrieve all comments belonging to an article or podcast episode as threaded conversations.\n\nIt will return the all top level comments with their nested comments as threads. See the format specification for further details.\n\nIt supports pagination, each page will contain `50` top level comments (and as many child comments they have) by default.\n\nIf the page parameter is not passed, all comments of an article or podcast will be returned.\n", "operationId": "getCommentsByArticleId", "parameters": [ @@ -1023,6 +1789,25 @@ "description": "A List of Comments", "content": { "application/json": { + "example": [ + { + "type_of": "comment", + "id_code": "1", + "created_at": "2023-12-15T00:15:35Z", + "body_html": "

Poutine try-hard migas.

\n\n", + "user": { + "name": "Denese \"Adriene\" \\:/ Leffler", + "username": "username121", + "twitter_username": "twitter121", + "github_username": "github121", + "user_id": 1001, + "website_url": null, + "profile_image": "/uploads/user/profile_image/1001/5e43495c-e279-4d6b-a6b9-3409c368d2f5.jpeg", + "profile_image_90": "/uploads/user/profile_image/1001/5e43495c-e279-4d6b-a6b9-3409c368d2f5.jpeg" + }, + "children": [] + } + ], "schema": { "type": "array", "items": { @@ -1033,7 +1818,15 @@ } }, "404": { - "description": "Resource Not Found" + "description": "Resource Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -1041,12 +1834,8 @@ "/api/comments/{id}": { "get": { "summary": "Comment by id", - "security": [ - - ], - "tags": [ - "comments" - ], + "security": [], + "tags": ["comments"], "description": "This endpoint allows the client to retrieve a comment as well as his descendants comments.\n\n It will return the required comment (the root) with its nested descendants as a thread.\n\n See the format specification for further details.", "operationId": "getCommentById", "parameters": [ @@ -1063,10 +1852,39 @@ ], "responses": { "200": { - "description": "A List of the Comments" + "description": "A List of the Comments", + "content": { + "application/json": { + "example": { + "type_of": "comment", + "id_code": "3", + "created_at": "2023-12-15T00:15:35Z", + "body_html": "

Food truck beard typewriter locavore. Lomo bushwick cardigan post-ironic vhs. Vinegar gluten-free swag tumblr helvetica listicle xoxo.

\n\n", + "user": { + "name": "Kennith \"Rodney\" \\:/ Baumbach", + "username": "username125", + "twitter_username": "twitter125", + "github_username": "github125", + "user_id": 1005, + "website_url": null, + "profile_image": "/uploads/user/profile_image/1005/a2641bae-3a16-4238-952a-04f14ce74d1a.jpeg", + "profile_image_90": "/uploads/user/profile_image/1005/a2641bae-3a16-4238-952a-04f14ce74d1a.jpeg" + }, + "children": [] + } + } + } }, "404": { - "description": "Comment Not Found" + "description": "Comment Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -1074,20 +1892,37 @@ "/api/follows/tags": { "get": { "summary": "Followed Tags", - "tags": [ - "followed_tags", - "tags" - ], + "tags": ["followed_tags", "tags"], "description": "This endpoint allows the client to retrieve a list of the tags they follow.", "operationId": "getFollowedTags", "responses": { "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "200": { "description": "A List of followed tags", "content": { "application/json": { + "example": [ + { + "id": 46, + "name": "tag3", + "points": 1.0 + }, + { + "id": 47, + "name": "tag4", + "points": 1.0 + } + ], "schema": { "type": "array", "items": { @@ -1103,9 +1938,7 @@ "/api/followers/users": { "get": { "summary": "Followers", - "tags": [ - "followers" - ], + "tags": ["followers"], "description": "This endpoint allows the client to retrieve a list of the followers they have.\n \"Followers\" are users that are following other users on the website.\n It supports pagination, each page will contain 80 followers by default.", "operationId": "getFollowers", "parameters": [ @@ -1131,6 +1964,28 @@ "description": "A List of followers", "content": { "application/json": { + "example": [ + { + "type_of": "user_follower", + "id": 6, + "created_at": "2023-12-15T00:15:36Z", + "user_id": 1012, + "name": "Moises \"Eddie\" \\:/ Johns", + "path": "/username132", + "username": "username132", + "profile_image": "/uploads/user/profile_image/1012/bdceff13-fec1-4040-8999-fda3de5d20ad.jpeg" + }, + { + "type_of": "user_follower", + "id": 5, + "created_at": "2023-12-15T00:15:36Z", + "user_id": 1010, + "name": "Bianca \"Bryan\" \\:/ Turcotte", + "path": "/username130", + "username": "username130", + "profile_image": "/uploads/user/profile_image/1010/2a301706-4678-449a-95b9-75e71500015b.jpeg" + } + ], "schema": { "type": "array", "items": { @@ -1169,7 +2024,15 @@ } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } } } } @@ -1177,12 +2040,8 @@ "/api/organizations/{username}": { "get": { "summary": "An organization (by username)", - "tags": [ - "organizations" - ], - "security": [ - - ], + "tags": ["organizations"], + "security": [], "description": "This endpoint allows the client to retrieve a single organization by their username", "operationId": "getOrganization", "parameters": [ @@ -1200,6 +2059,22 @@ "description": "An Organization", "content": { "application/json": { + "example": { + "type_of": "organization", + "id": 12, + "username": "org12", + "name": "Sporer Inc", + "summary": "Biodiesel sustainable letterpress portland chartreuse. Authentic post-ironic everyday skateboard franzen thundercats.", + "twitter_username": "org2359", + "github_username": "org5109", + "url": "http://baumbach-smitham.net/lavonia_emard", + "location": null, + "tech_stack": null, + "tag_line": null, + "story": null, + "joined_at": "2023-12-15T00:15:36Z", + "profile_image": "/uploads/organization/profile_image/12/f0728df4-7a41-474c-8675-aa2b31a8d84c.png" + }, "schema": { "type": "object", "items": { @@ -1210,7 +2085,15 @@ } }, "404": { - "description": "Not Found" + "description": "Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -1218,13 +2101,8 @@ "/api/organizations/{organization_id_or_username}/users": { "get": { "summary": "Organization's users", - "tags": [ - "organizations", - "users" - ], - "security": [ - - ], + "tags": ["organizations", "users"], + "security": [], "description": "This endpoint allows the client to retrieve a list of users belonging to the organization\n\nIt supports pagination, each page will contain `30` users by default.", "operationId": "getOrgUsers", "parameters": [ @@ -1248,6 +2126,34 @@ "description": "An Organization's users (with ID)", "content": { "application/json": { + "example": [ + { + "type_of": "user", + "id": 1025, + "username": "username145", + "name": "Lennie \"Lacy\" \\:/ Jaskolski", + "twitter_username": "twitter145", + "github_username": "github145", + "summary": null, + "location": null, + "website_url": null, + "joined_at": "Dec 15, 2023", + "profile_image": "/uploads/user/profile_image/1025/6c6a3071-cc34-41fb-9a03-811eca66e09d.jpeg" + }, + { + "type_of": "user", + "id": 1026, + "username": "username146", + "name": "Iva \"Deon\" \\:/ Bernhard", + "twitter_username": "twitter146", + "github_username": "github146", + "summary": null, + "location": null, + "website_url": null, + "joined_at": "Dec 15, 2023", + "profile_image": "/uploads/user/profile_image/1026/08be477d-61b1-4140-b1af-0608ba9fc9cd.jpeg" + } + ], "schema": { "type": "array", "items": { @@ -1258,7 +2164,15 @@ } }, "404": { - "description": "Not Found" + "description": "Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -1266,13 +2180,8 @@ "/api/organizations/{organization_id_or_username}/articles": { "get": { "summary": "Organization's Articles", - "tags": [ - "organizations", - "articles" - ], - "security": [ - - ], + "tags": ["organizations", "articles"], + "security": [], "description": "This endpoint allows the client to retrieve a list of Articles belonging to the organization\n\nIt supports pagination, each page will contain `30` users by default.", "operationId": "getOrgArticles", "parameters": [ @@ -1296,6 +2205,51 @@ "description": "An Organization's Articles (with ID)", "content": { "application/json": { + "example": [ + { + "type_of": "article", + "id": 27, + "title": "To Say Nothing of the Dog26", + "description": "Readymade literally fanny pack squid slow-carb crucifix mumblecore authentic. Kickstarter put a bird...", + "readable_publish_date": "Dec 15", + "slug": "to-say-nothing-of-the-dog26-2oic", + "path": "/org18/to-say-nothing-of-the-dog26-2oic", + "url": "http://forem.test/org18/to-say-nothing-of-the-dog26-2oic", + "comments_count": 0, + "public_reactions_count": 0, + "collection_id": null, + "published_timestamp": "2023-12-15T00:15:37Z", + "positive_reactions_count": 0, + "cover_image": "http://forem.test/assets/14-5e64731cc7cd63e3b689647d9d3c3e4e1d907690c716d3dd1e356466726a2c2d.png", + "social_image": "http://forem.test/assets/14-5e64731cc7cd63e3b689647d9d3c3e4e1d907690c716d3dd1e356466726a2c2d.png", + "canonical_url": "http://forem.test/org18/to-say-nothing-of-the-dog26-2oic", + "created_at": "2023-12-15T00:15:37Z", + "edited_at": null, + "crossposted_at": null, + "published_at": "2023-12-15T00:15:37Z", + "last_comment_at": "2023-12-15T00:15:37Z", + "reading_time_minutes": 1, + "tag_list": ["javascript", "html", "discuss"], + "tags": "javascript, html, discuss", + "user": { + "name": "Emmett \"Lanelle\" \\:/ Hills", + "username": "username156", + "twitter_username": "twitter156", + "github_username": "github156", + "user_id": 1036, + "website_url": null, + "profile_image": "/uploads/user/profile_image/1036/11b2f734-0a30-48a0-926f-13ff058d4c93.jpeg", + "profile_image_90": "/uploads/user/profile_image/1036/11b2f734-0a30-48a0-926f-13ff058d4c93.jpeg" + }, + "organization": { + "name": "Lindgren-Marquardt", + "username": "org18", + "slug": "org18", + "profile_image": "/uploads/organization/profile_image/18/040332d6-91a3-43e2-b13a-60d17688bf40.png", + "profile_image_90": "/uploads/organization/profile_image/18/040332d6-91a3-43e2-b13a-60d17688bf40.png" + } + } + ], "schema": { "type": "array", "items": { @@ -1306,7 +2260,15 @@ } }, "404": { - "description": "Not Found" + "description": "Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -1314,12 +2276,8 @@ "/api/organizations": { "get": { "summary": "Organizations", - "tags": [ - "organizations" - ], - "security": [ - - ], + "tags": ["organizations"], + "security": [], "description": "This endpoint allows the client to retrieve a list of Dev organizations.\n\n It supports pagination, each page will contain 10 tags by default.", "operationId": "getOrganizations", "parameters": [ @@ -1335,6 +2293,19 @@ "description": "A list of all organizations", "content": { "application/json": { + "example": [ + { + "id": 20, + "name": "Klein Group", + "profile_image": { + "url": "/uploads/organization/profile_image/20/6656819e-edbd-4af2-97de-b4457cdfd026.png" + }, + "slug": "org20", + "summary": "Lomo food truck pour-over. 90's quinoa authentic diy muggle magic butcher ethical. Cliche tattooed mustache ethical.", + "tag_line": null, + "url": "http://skiles.org/woodrow" + } + ], "schema": { "type": "array", "items": { @@ -1348,23 +2319,40 @@ }, "post": { "summary": "Create an Organization", - "tags": [ - "organizations" - ], + "tags": ["organizations"], "description": "This endpoint allows the client to create an organization with the provided parameters.\n It requires a token from a user with `admin` privileges.", "operationId": "createOrganization", - "parameters": [ - - ], + "parameters": [], "responses": { "201": { - "description": "Successful" + "description": "Successful", + "content": { + "application/json": { + "example": { + "id": 23, + "name": "New Test Org", + "profile_image": "uploads/organization/profile_image/1/400x400.jpg", + "slug": "org10001", + "summary": "a newly created test org", + "tag_line": "a test org's tagline", + "url": "https://testorg.io" + } + } + } }, "401": { "description": "Unauthorized" }, "422": { - "description": "Unprocessable Entity" + "description": "Unprocessable Entity", + "content": { + "application/json": { + "example": { + "error": "Validation failed: Name can't be blank, Profile image can't be blank, Slug can't be blank", + "status": 422 + } + } + } } }, "requestBody": { @@ -1381,12 +2369,8 @@ "/api/organizations/{id}": { "get": { "summary": "An organization (by id)", - "tags": [ - "organizations" - ], - "security": [ - - ], + "tags": ["organizations"], + "security": [], "description": "This endpoint allows the client to retrieve a single organization by their id", "operationId": "getOrganizationById", "parameters": [ @@ -1404,6 +2388,22 @@ "description": "An Organization", "content": { "application/json": { + "example": { + "type_of": "organization", + "id": 21, + "username": "org21", + "name": "Wehner, Skiles and Reinger", + "summary": "Lo-fi tattooed master single-origin coffee umami.", + "twitter_username": "org7509", + "github_username": "org9430", + "url": "http://haag-stark.io/fredrick.tillman", + "location": null, + "tech_stack": null, + "tag_line": null, + "story": null, + "joined_at": "2023-12-15T00:15:37Z", + "profile_image": "/uploads/organization/profile_image/21/38405e52-b2b2-47d3-a55d-2e908713fe06.png" + }, "schema": { "type": "object", "items": { @@ -1414,15 +2414,21 @@ } }, "404": { - "description": "Not Found" + "description": "Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } }, "put": { "summary": "Update an organization by id", - "tags": [ - "organizations" - ], + "tags": ["organizations"], "description": "This endpoint allows the client to update an existing organization.", "parameters": [ { @@ -1440,16 +2446,53 @@ ], "responses": { "200": { - "description": "An Organization" + "description": "An Organization", + "content": { + "application/json": { + "example": { + "id": 24, + "name": "Reinger, Gleichner and Rogahn", + "profile_image": "/uploads/organization/profile_image/24/1b9f0d37-acb7-4b80-b950-d621f5c5463d.png", + "slug": "org23", + "summary": "An updated summary for the organization.", + "tag_line": null, + "url": "http://kertzmann.io/clora.mcglynn" + } + } + } }, "404": { - "description": "organization Not Found" + "description": "organization Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "422": { - "description": "Unprocessable Entity" + "description": "Unprocessable Entity", + "content": { + "application/json": { + "example": { + "error": "param is missing or the value is empty: organization", + "status": 422 + } + } + } } }, "requestBody": { @@ -1464,9 +2507,7 @@ }, "delete": { "summary": "Delete an Organization by id", - "tags": [ - "organizations" - ], + "tags": ["organizations"], "description": "This endpoint allows the client to delete a single organization, specified by id", "parameters": [ { @@ -1484,10 +2525,26 @@ ], "responses": { "200": { - "description": "successful" + "description": "successful", + "content": { + "application/json": { + "example": { + "message": "deletion scheduled for organization with ID 28", + "status": 200 + } + } + } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } } } } @@ -1495,18 +2552,32 @@ "/api/pages": { "get": { "summary": "show details for all pages", - "security": [ - - ], - "tags": [ - "pages" - ], + "security": [], + "tags": ["pages"], "description": "This endpoint allows the client to retrieve details for all Page objects.", "responses": { "200": { "description": "successful", "content": { "application/json": { + "example": [ + { + "id": 1, + "title": "For Whom the Bell Tolls", + "slug": "onion-book", + "description": "Reprehenderit quia et voluptate.", + "is_top_level_path": false, + "landing_page": false, + "body_html": null, + "body_json": null, + "body_markdown": "Enim pariatur aut ea.", + "processed_html": "

Enim pariatur aut ea.

\n\n", + "social_image": { + "url": null + }, + "template": "contained" + } + ], "schema": { "type": "array", "items": { @@ -1520,22 +2591,66 @@ }, "post": { "summary": "pages", - "tags": [ - "pages" - ], + "tags": ["pages"], "description": "This endpoint allows the client to create a new page.", - "parameters": [ - - ], + "parameters": [], "responses": { "200": { - "description": "successful" + "description": "successful", + "content": { + "application/json": { + "example": { + "id": 3, + "title": "Example Page", + "slug": "example1", + "description": "a new page", + "is_top_level_path": false, + "landing_page": false, + "body_html": null, + "body_json": null, + "body_markdown": "# Hi, this is a New Page\nYep, it's an a new page", + "processed_html": "

\n \n \n Hi, this is a New Page\n

\n\n

Yep, it's an a new page

\n\n", + "social_image": { + "url": null + }, + "template": "contained" + } + } + } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "422": { - "description": "unprocessable" + "description": "unprocessable", + "content": { + "application/json": { + "example": { + "id": null, + "title": "Example Page", + "slug": "example1", + "description": "a new page", + "is_top_level_path": false, + "landing_page": false, + "body_html": null, + "body_json": null, + "body_markdown": "# Hi, this is a New Page\nYep, it's an a new page", + "processed_html": null, + "social_image": { + "url": null + }, + "template": "moon" + } + } + } } }, "requestBody": { @@ -1589,12 +2704,8 @@ "/api/pages/{id}": { "get": { "summary": "show details for a page", - "security": [ - - ], - "tags": [ - "pages" - ], + "security": [], + "tags": ["pages"], "description": "This endpoint allows the client to retrieve details for a single Page object, specified by ID.", "parameters": [ { @@ -1615,6 +2726,22 @@ "description": "successful", "content": { "application/json": { + "example": { + "id": 6, + "title": "Dance Dance Dance", + "slug": "cutting_deport", + "description": "Vitae eos corrupti explicabo.", + "is_top_level_path": false, + "landing_page": false, + "body_html": null, + "body_json": null, + "body_markdown": "Atque aut qui nisi.", + "processed_html": "

Atque aut qui nisi.

\n\n", + "social_image": { + "url": null + }, + "template": "contained" + }, "schema": { "$ref": "#/components/schemas/Page" } @@ -1625,9 +2752,7 @@ }, "put": { "summary": "update details for a page", - "tags": [ - "pages" - ], + "tags": ["pages"], "description": "This endpoint allows the client to retrieve details for a single Page object, specified by ID.", "parameters": [ { @@ -1648,6 +2773,22 @@ "description": "successful", "content": { "application/json": { + "example": { + "id": 7, + "title": "New Title", + "slug": "distant_provision", + "description": "Qui aspernatur illo aut.", + "is_top_level_path": false, + "landing_page": false, + "body_html": null, + "body_json": null, + "body_markdown": "Quia rerum officiis quod.", + "processed_html": "

Quia rerum officiis quod.

\n\n", + "social_image": { + "url": null + }, + "template": "contained" + }, "schema": { "$ref": "#/components/schemas/Page" } @@ -1655,10 +2796,38 @@ } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "422": { - "description": "unprocessable" + "description": "unprocessable", + "content": { + "application/json": { + "example": { + "id": 9, + "title": "Behold the Man", + "slug": "kidnap-outside", + "description": "Blanditiis sunt vel inventore.", + "is_top_level_path": false, + "landing_page": false, + "body_html": null, + "body_json": null, + "body_markdown": "Voluptatum fugit quisquam occaecati.", + "processed_html": "

Voluptatem beatae provident sed.

\n\n", + "social_image": { + "url": null + }, + "template": "moon" + } + } + } } }, "requestBody": { @@ -1673,9 +2842,7 @@ }, "delete": { "summary": "remove a page", - "tags": [ - "pages" - ], + "tags": ["pages"], "description": "This endpoint allows the client to delete a single Page object, specified by ID.", "parameters": [ { @@ -1696,6 +2863,22 @@ "description": "successful", "content": { "application/json": { + "example": { + "id": 10, + "title": "After Many a Summer Dies the Swan", + "slug": "exhibition_nursery", + "description": "Doloribus blanditiis delectus quibusdam.", + "is_top_level_path": false, + "landing_page": false, + "body_html": null, + "body_json": null, + "body_markdown": "Laudantium sit quod blanditiis.", + "processed_html": "

Laudantium sit quod blanditiis.

\n\n", + "social_image": { + "url": null + }, + "template": "contained" + }, "schema": { "$ref": "#/components/schemas/Page" } @@ -1703,10 +2886,31 @@ } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "422": { - "description": "unprocessable" + "description": "unprocessable", + "content": { + "application/json": { + "example": { + "doubled_module": { + "const_name": "Page", + "object": "Page" + }, + "__expired": false, + "name": null, + "__sending_message": null + } + } + } } } } @@ -1714,12 +2918,8 @@ "/api/podcast_episodes": { "get": { "summary": "Podcast Episodes", - "security": [ - - ], - "tags": [ - "podcast_episodes" - ], + "security": [], + "tags": ["podcast_episodes"], "description": "This endpoint allows the client to retrieve a list of podcast episodes.\n \"Podcast episodes\" are episodes belonging to podcasts.\n It will only return active (reachable) podcast episodes that belong to published podcasts available on the platform, ordered by descending publication date.\n It supports pagination, each page will contain 30 articles by default.", "operationId": "getPodcastEpisodes", "parameters": [ @@ -1745,6 +2945,21 @@ "description": "A List of Podcast episodes filtered by username", "content": { "application/json": { + "example": [ + { + "type_of": "podcast_episodes", + "class_name": "PodcastEpisode", + "id": 2, + "path": "/codenewbie/slug-2", + "title": "14", + "image_url": "/uploads/podcast/image/2/74b69d3b-45d0-4dcc-82e8-aae16f3f3afb.jpeg", + "podcast": { + "title": "Brooklyn Black", + "slug": "codenewbie", + "image_url": "/uploads/podcast/image/2/74b69d3b-45d0-4dcc-82e8-aae16f3f3afb.jpeg" + } + } + ], "schema": { "type": "array", "items": { @@ -1755,7 +2970,15 @@ } }, "404": { - "description": "Unknown Podcast username" + "description": "Unknown Podcast username", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -1763,9 +2986,7 @@ "/api/profile_images/{username}": { "get": { "summary": "A Users or organizations profile image", - "tags": [ - "profile images" - ], + "tags": ["profile images"], "description": "This endpoint allows the client to retrieve a user or organization profile image information by its\n corresponding username.", "operationId": "getProfileImage", "parameters": [ @@ -1785,6 +3006,12 @@ "description": "An object containing profile image details", "content": { "application/json": { + "example": { + "type_of": "profile_image", + "image_of": "user", + "profile_image": "/uploads/user/profile_image/1060/88048a31-60e1-41af-8d6d-5a5bd37a6767.jpeg", + "profile_image_90": "/uploads/user/profile_image/1060/88048a31-60e1-41af-8d6d-5a5bd37a6767.jpeg" + }, "schema": { "type": "object", "items": { @@ -1795,7 +3022,15 @@ } }, "404": { - "description": "Resource Not Found" + "description": "Resource Not Found", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -1803,9 +3038,7 @@ "/api/reactions/toggle": { "post": { "summary": "toggle reaction", - "tags": [ - "reactions" - ], + "tags": ["reactions"], "description": "This endpoint allows the client to toggle the user's reaction to a specified reactable (eg, Article, Comment, or User). For examples:\n * \"Like\"ing an Article will create a new \"like\" Reaction from the user for that Articles\n * \"Like\"ing that Article a second time will remove the \"like\" from the user", "parameters": [ { @@ -1838,20 +3071,35 @@ "required": true, "schema": { "type": "string", - "enum": [ - "Comment", - "Article", - "User" - ] + "enum": ["Comment", "Article", "User"] } } ], "responses": { "200": { - "description": "successful" + "description": "successful", + "content": { + "application/json": { + "example": { + "result": "create", + "category": "like", + "id": 1, + "reactable_id": 29, + "reactable_type": "Article" + } + } + } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } } } } @@ -1859,9 +3107,7 @@ "/api/reactions": { "post": { "summary": "create reaction", - "tags": [ - "reactions" - ], + "tags": ["reactions"], "description": "This endpoint allows the client to create a reaction to a specified reactable (eg, Article, Comment, or User). For examples:\n * \"Like\"ing an Article will create a new \"like\" Reaction from the user for that Articles\n * \"Like\"ing that Article a second time will return the previous \"like\"", "parameters": [ { @@ -1894,20 +3140,35 @@ "required": true, "schema": { "type": "string", - "enum": [ - "Comment", - "Article", - "User" - ] + "enum": ["Comment", "Article", "User"] } } ], "responses": { "200": { - "description": "successful" + "description": "successful", + "content": { + "application/json": { + "example": { + "result": "none", + "category": "like", + "id": 3, + "reactable_id": 31, + "reactable_type": "Article" + } + } + } }, "401": { - "description": "unauthorized" + "description": "unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } } } } @@ -1915,9 +3176,7 @@ "/api/readinglist": { "get": { "summary": "Readinglist", - "tags": [ - "readinglist" - ], + "tags": ["readinglist"], "description": "This endpoint allows the client to retrieve a list of articles that were saved to a Users readinglist.\n It supports pagination, each page will contain `30` articles by default", "operationId": "getReadinglist", "parameters": [ @@ -1930,12 +3189,21 @@ ], "responses": { "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "200": { "description": "A list of articles in the users readinglist", "content": { "application/json": { + "example": [], "schema": { "type": "array", "items": { @@ -1951,12 +3219,8 @@ "/api/tags": { "get": { "summary": "Tags", - "tags": [ - "tags" - ], - "security": [ - - ], + "tags": ["tags"], + "security": [], "description": "This endpoint allows the client to retrieve a list of tags that can be used to tag articles.\n\nIt will return tags ordered by popularity.\n\nIt supports pagination, each page will contain 10 tags by default.", "operationId": "getTags", "parameters": [ @@ -1972,6 +3236,26 @@ "description": "A List of all tags", "content": { "application/json": { + "example": [ + { + "id": 86, + "name": "tag7", + "bg_color_hex": null, + "text_color_hex": null + }, + { + "id": 85, + "name": "tag6", + "bg_color_hex": null, + "text_color_hex": null + }, + { + "id": 84, + "name": "tag5", + "bg_color_hex": null, + "text_color_hex": null + } + ], "schema": { "type": "array", "items": { @@ -1987,9 +3271,7 @@ "/api/users/{id}/suspend": { "put": { "summary": "Suspend a User", - "tags": [ - "users" - ], + "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": [ @@ -2011,10 +3293,26 @@ "description": "User successfully unpublished" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Unknown User ID" + "description": "Unknown User ID", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -2022,9 +3320,7 @@ "/api/users/{id}/limited": { "put": { "summary": "Add limited role for a User", - "tags": [ - "users" - ], + "tags": ["users"], "description": "This endpoint allows the client to limit 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 'limited' role. Limiting a user will limit notifications\ngenerated from new posts and comments. It doesn't delete any of the user's content or prevent them\nfrom generating new content while limited. Users are not notified of their limits\nin the UI, so if you want them to know about this, you must notify them.", "operationId": "limitUser", "parameters": [ @@ -2046,18 +3342,32 @@ "description": "User successfully limited" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Unknown User ID" + "description": "Unknown User ID", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } }, "delete": { "summary": "Remove limited for a User", - "tags": [ - "users" - ], + "tags": ["users"], "description": "This endpoint allows the client to remove limits for a user.\n\nThe user associated with the API key must have any 'admin' or 'moderator' role.\n\nThis specified user will be restored to 'general' status. Users are not notified\nof limits in the UI, so if you want them to know about this, you must\nnotify them.", "operationId": "unLimitUser", "parameters": [ @@ -2079,10 +3389,26 @@ "description": "User successfully un-limited" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Unknown User ID" + "description": "Unknown User ID", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -2090,9 +3416,7 @@ "/api/users/{id}/spam": { "put": { "summary": "Add spam role for a User", - "tags": [ - "users" - ], + "tags": ["users"], "description": "This endpoint allows the client to add the spam role to a user.\n\n The user associated with the API key must have any 'admin' or 'moderator' role.\n\n This specified user will be assigned the 'spam' role. Addding the spam role to a user will stop the\n user from posting new posts and comments. It doesn't delete any of the user's content, just\n prevents them from creating new content while having the spam role. Users are not notified of their spaminess\n in the UI, so if you want them to know about this, you must notify them", "operationId": "spamUser", "parameters": [ @@ -2114,18 +3438,32 @@ "description": "Spam role assigned to the user successfully" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Unknown User ID" + "description": "Unknown User ID", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } }, "delete": { "summary": "Remove spam role from a User", - "tags": [ - "users" - ], + "tags": ["users"], "description": "This endpoint allows the client to remove the spam role for a user.\n\n The user associated with the API key must have any 'admin' or 'moderator' role.\n\n This specified user will be restored to 'general' status. Users are not notified\n of removing their spam role in the UI, so if you want them to know about this, you must\n notify them.", "operationId": "unSpamUser", "parameters": [ @@ -2147,10 +3485,122 @@ "description": "Successfully removed the spam role from a user" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Unknown User ID" + "description": "Unknown User ID", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } + } + } + } + }, + "/api/users/{id}/trusted": { + "put": { + "summary": "Add trusted role for a User", + "tags": ["users"], + "description": "This endpoint allows the client to add the trusted role to a user.\n The user associated with the API key must have an 'admin' or 'moderator' role.\n The specified user will be assigned the 'trusted' role. Adding the trusted role to a user\n allows them to upvote and downvote posts and flag content that needs investigating by admins.\n Users are notified of this change in the UI, and by email.", + "operationId": "trustUser", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The ID of the user to assign the trusted role.", + "schema": { + "type": "integer", + "format": "int32", + "minimum": 1 + }, + "example": 1 + } + ], + "responses": { + "204": { + "description": "Trusted role assigned to the user successfully" + }, + "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 + } + } + } + } + } + }, + "delete": { + "summary": "Remove trusted role from a User", + "tags": ["users"], + "description": "This endpoint allows the client to remove the trusted role for a user.\n The user associated with the API key must have an 'admin' or 'moderator' role.\n The specified user will be restored to 'general' status. Users are not notified\n of removing their trusted role in the UI, so if you want them to know about this, you must\n notify them.", + "operationId": "unTrustUser", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "description": "The ID of the user to remove the trusted role from.", + "schema": { + "type": "integer", + "format": "int32", + "minimum": 1 + }, + "example": 1 + } + ], + "responses": { + "204": { + "description": "Successfully removed the trusted role from a user" + }, + "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 + } + } + } } } } @@ -2158,9 +3608,7 @@ "/api/users/me": { "get": { "summary": "The authenticated user", - "tags": [ - "users" - ], + "tags": ["users"], "description": "This endpoint allows the client to retrieve information about the authenticated user", "operationId": "getUserMe", "responses": { @@ -2168,6 +3616,22 @@ "description": "successful", "content": { "application/json": { + "example": { + "type_of": "user", + "id": 1110, + "username": "username230", + "name": "Erich \"Ewa\" \\:/ Prohaska", + "twitter_username": "twitter230", + "github_username": "github230", + "email": null, + "summary": null, + "location": null, + "website_url": null, + "joined_at": "Dec 15, 2023", + "profile_image": "/uploads/user/profile_image/1110/9f031b2e-e68a-445f-b577-f46210dc14cf.jpeg", + "badge_ids": [], + "followers_count": 0 + }, "schema": { "type": "object", "items": { @@ -2178,7 +3642,15 @@ } }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } } } } @@ -2186,9 +3658,7 @@ "/api/users/{id}": { "get": { "summary": "A User", - "tags": [ - "users" - ], + "tags": ["users"], "description": "This endpoint allows the client to retrieve a single user, either by id\nor by the user's username.\n\nFor complete documentation, see the v0 API docs: https://developers.forem.com/api/v0#tag/users/operation/getUser", "operationId": "getUser", "parameters": [ @@ -2221,9 +3691,7 @@ "/api/users/{id}/unpublish": { "put": { "summary": "Unpublish a User's Articles and Comments", - "tags": [ - "users" - ], + "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": [ @@ -2245,10 +3713,26 @@ "description": "User's articles and comments successfully unpublished" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "404": { - "description": "Unknown User ID (still accepted for async processing)" + "description": "Unknown User ID (still accepted for async processing)", + "content": { + "application/json": { + "example": { + "error": "not found", + "status": 404 + } + } + } } } } @@ -2256,23 +3740,35 @@ "/api/admin/users": { "post": { "summary": "Invite a User", - "tags": [ - "users" - ], + "tags": ["users"], "description": "This endpoint allows the client to trigger an invitation to the provided email address.\n\n It requires a token from a user with `super_admin` privileges.", "operationId": "postAdminUsersCreate", - "parameters": [ - - ], + "parameters": [], "responses": { "200": { "description": "Successful" }, "401": { - "description": "Unauthorized" + "description": "Unauthorized", + "content": { + "application/json": { + "example": { + "error": "unauthorized", + "status": 401 + } + } + } }, "422": { - "description": "Unprocessable Entity" + "description": "Unprocessable Entity", + "content": { + "application/json": { + "example": { + "error": "param is missing or the value is empty: email", + "status": 422 + } + } + } } }, "requestBody": { @@ -2289,13 +3785,8 @@ "/api/videos": { "get": { "summary": "Articles with a video", - "tags": [ - "videos", - "articles" - ], - "security": [ - - ], + "tags": ["videos", "articles"], + "security": [], "description": "This endpoint allows the client to retrieve a list of articles that are uploaded with a video.\n\nIt will only return published video articles ordered by descending popularity.\n\nIt supports pagination, each page will contain 24 articles by default.", "operationId": "videos", "parameters": [ @@ -2311,6 +3802,34 @@ "description": "A List of all articles with videos", "content": { "application/json": { + "example": [ + { + "type_of": "video_article", + "id": 34, + "path": "/username244/brandy-of-the-damned33-2cbm", + "cloudinary_video_url": "https://dw71fyauz7yz9.cloudfront.net/video-upload__1e42eb0bab4abb3c63baeb5e8bdfe69f/thumbs-video-upload__1e42eb0bab4abb3c63baeb5e8bdfe69f-00001.png", + "title": "Brandy of the Damned33", + "user_id": 1125, + "video_duration_in_minutes": "00:00", + "video_source_url": "https://dw71fyauz7yz9.cloudfront.net/video-upload__1e42eb0bab4abb3c63baeb5e8bdfe69f/video-upload__1e42eb0bab4abb3c63baeb5e8bdfe69f.m3u8", + "user": { + "name": "Effie \"Kieth\" \\:/ Weimann" + } + }, + { + "type_of": "video_article", + "id": 33, + "path": "/username243/o-pioneers32-1ejl", + "cloudinary_video_url": "https://dw71fyauz7yz9.cloudfront.net/video-upload__1e42eb0bab4abb3c63baeb5e8bdfe69f/thumbs-video-upload__1e42eb0bab4abb3c63baeb5e8bdfe69f-00001.png", + "title": "O Pioneers!32", + "user_id": 1124, + "video_duration_in_minutes": "00:00", + "video_source_url": "https://dw71fyauz7yz9.cloudfront.net/video-upload__1e42eb0bab4abb3c63baeb5e8bdfe69f/video-upload__1e42eb0bab4abb3c63baeb5e8bdfe69f.m3u8", + "user": { + "name": "Preston \"Darius\" \\:/ Runolfsdottir" + } + } + ], "schema": { "type": "array", "items": { @@ -2332,9 +3851,7 @@ ], "security": [ { - "api-key": [ - - ] + "api-key": [] } ], "components": { @@ -2729,11 +4246,7 @@ "format": "float" } }, - "required": [ - "id", - "name", - "points" - ] + "required": ["id", "name", "points"] }, "Tag": { "description": "Representation of a tag", @@ -2803,12 +4316,7 @@ "description": "Controls what kind of layout the page is rendered in" } }, - "required": [ - "title", - "slug", - "description", - "template" - ] + "required": ["title", "slug", "description", "template"] }, "PodcastEpisodeIndex": { "description": "Representation of a podcast episode returned in a list", @@ -3226,30 +4734,18 @@ }, "display_to": { "type": "string", - "enum": [ - "all", - "logged_in", - "logged_out" - ], + "enum": ["all", "logged_in", "logged_out"], "default": "all", "description": "Potentially limits visitors to whom the ad is visible" }, "type_of": { "type": "string", - "enum": [ - "in_house", - "community", - "external" - ], + "enum": ["in_house", "community", "external"], "default": "in_house", "description": "Types of the billboards:\nin_house (created by admins),\ncommunity (created by an entity, appears on entity's content),\nexternal ( created by an entity, or a non-entity, can appear everywhere)\n" } }, - "required": [ - "name", - "body_markdown", - "placement_area" - ] + "required": ["name", "body_markdown", "placement_area"] }, "Segment": { "description": "A manually managed audience segment", @@ -3261,9 +4757,7 @@ }, "type_of": { "type": "string", - "enum": [ - "manual" - ], + "enum": ["manual"], "default": "manual", "description": "Marks the segment as manually managed (other types are internal)" }, @@ -3287,4 +4781,4 @@ } } } -} \ No newline at end of file +}