API: OpenAPI 3 spec and auto generated docs (#3650)

This commit is contained in:
rhymes 2019-08-12 17:36:04 +02:00 committed by Mac Siri
parent 4a45996bfc
commit 35526bef9b
18 changed files with 1638 additions and 388 deletions

2
.gitignore vendored
View file

@ -58,3 +58,5 @@ _docker-storage/
# YARD generated doc (in the Gitdocs folder)
docs/.static/ruby-doc/
# ReDoc generated API doc (in the Gitdocs folder)
docs/.static/api/

View file

@ -31,12 +31,7 @@ module Api
end
def show
relation = Article.published.includes(:user)
@article = if params[:id] == "by_path"
relation.find_by!(path: params[:url]).decorate
else
relation.find(params[:id]).decorate
end
@article = Article.published.includes(:user).find(params[:id]).decorate
end
def onboarding

View file

@ -53,7 +53,6 @@ class CacheBuster
bust_home_pages(article)
bust_tag_pages(article)
bust("/api/articles/#{article.id}")
bust("/api/articles/by_path?url=#{article.path}")
return unless article.collection_id
article.collection&.articles&.find_each do |a|

View file

@ -14,31 +14,36 @@ json.array! @articles do |article|
json.positive_reactions_count article.positive_reactions_count
json.published_timestamp article.published_timestamp
user = article.user
user_profile_image = ProfileImage.new(article.user)
json.user do
json.name article.user.name
json.username article.user.username
json.twitter_username article.user.twitter_username
json.github_username article.user.github_username
json.website_url article.user.processed_website_url
json.profile_image ProfileImage.new(article.user).get(640)
json.profile_image_90 ProfileImage.new(article.user).get(90)
json.name user.name
json.username user.username
json.twitter_username user.twitter_username
json.github_username user.github_username
json.website_url user.processed_website_url
json.profile_image user_profile_image.get(640)
json.profile_image_90 user_profile_image.get(90)
end
if article.organization
organization = article.organization
organization_profile_image = ProfileImage.new(article.organization)
json.organization do
json.name article.organization.name
json.username article.organization.username
json.slug article.organization.slug
json.profile_image ProfileImage.new(article.organization).get(640)
json.profile_image_90 ProfileImage.new(article.organization).get(90)
json.name organization.name
json.username organization.username
json.slug organization.slug
json.profile_image organization_profile_image.get(640)
json.profile_image_90 organization_profile_image.get(90)
end
end
if FlareTag.new(article).tag
flare_tag = FlareTag.new(article).tag
if flare_tag
json.flare_tag do
json.name FlareTag.new(article).tag.name
json.bg_color_hex FlareTag.new(article).tag.bg_color_hex
json.text_color_hex FlareTag.new(article).tag.text_color_hex
json.name flare_tag.name
json.bg_color_hex flare_tag.bg_color_hex
json.text_color_hex flare_tag.text_color_hex
end
end
end

View file

@ -22,8 +22,6 @@ json.last_comment_at @article.last_comment_at&.utc&.iso8601
json.body_html @article.processed_html
json.body_markdown @article.body_markdown
json.ltag_style(@article.liquid_tags_used.map { |ltag| Rails.application.assets["ltags/#{ltag}.css"].to_s.html_safe })
json.ltag_script(@article.liquid_tags_used.map { |ltag| ltag.script.html_safe })
json.user do
json.name @article.user.name

View file

@ -1,331 +0,0 @@
<% title "DEV API" %>
<%= content_for :page_meta do %>
<link rel="canonical" href="https://dev.to/api" />
<meta name="description" content="<%= ApplicationConfig["COMMUNITY_NAME"] %> API info and guides">
<meta name="keywords" content="software development,engineering,rails,javascript,ruby,security">
<meta property="og:type" content="article" />
<meta property="og:url" content="https://dev.to/api" />
<meta property="og:title" content="<%= ApplicationConfig["COMMUNITY_NAME"] %> API" />
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@<%= ApplicationConfig["SITE_TWITTER_HANDLE"] %>">
<meta name="twitter:title" content="<%= ApplicationConfig["COMMUNITY_NAME"] %> API">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/themes/prism-tomorrow.css" integrity="sha256-0dkohC9ZEupqWbq0hS5cVR4QQXJ+mp6N2oJyuks6gt0=" crossorigin="anonymous" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/prism.min.js"
integrity="sha256-NFZVyNmS1YlmiklazBA+TALYJlJtZj/y/i/oADk6CVE=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-bash.min.js"
integrity="sha256-0W9ddRPtgrjvZVUxGhU/ShLxFi3WGNV2T7A7bBTuDWo=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-json.min.js"
integrity="sha256-18m89UBQcWGjPHHo64UD+sQx4SpMxiRI1F0MbefKXWw=" crossorigin="anonymous" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.16.0/components/prism-markdown.min.js"
integrity="sha256-e4izlzFmEQlenZQnzkYK5oyxV5mX6lwVQjL6onkHiy0=" crossorigin="anonymous"></script>
<% end %>
<div class="blank-space"></div>
<div class="container article">
<div class="title">
<h1>
DEV Articles API (beta)
</h1>
<h3>
As of May 16th 2019 DEV has released a beta version of a REST API to create or update articles.
Check back to this page for all updates related to the API.
</h3>
</div>
<div class="body">
<div class="body" style="width: 90%;">
<h2>
<a name="getting-an-access-token" href="#getting-an-access-token" class="anchor">
</a>
About the Articles API
</h2>
<p>
"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
<code>article</code> within the code.
They are different from <a href="/listings">listings</a>, comments, podcasts, etc.
</p>
<h2>
<a name="getting-an-access-token" href="#getting-an-access-token" class="anchor">
</a>
Getting an access token
</h2>
<p>Authentication for the Articles API requires the <em>DEV API access token.</em> To obtain one, please follow these steps:</p>
<ul>
<li>Connect to <a href="https://dev.to/settings/account">https://dev.to/settings/account</a>
</li>
<li>
<p>In the <strong>DEV API Access Tokens</strong> section create a new token by adding a description and clicking on "Generate Token":</p>
<p></p>
<p>
<img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F4Ya4WgC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/4zm0cbutj9o9eau0jm9t.png" alt="DEV Access tokens page" loading="lazy">
</p>
</li>
<li><p>You'll see the newly generated token in the same view:</p></li>
</ul>
<p>
<img src="https://res.cloudinary.com/practicaldev/image/fetch/s--kp5L6-9G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ra01mo6fp1es261db2ho.png" alt="DEV access tokens page with a generated token" loading="lazy">
</p>
<h2>
<a name="creating-an-article-through-the-api" href="#creating-an-article-through-the-api" class="anchor">
</a>
Creating an article through the API
</h2>
<p>Here is an example of how to create an article:</p>
<div class="highlight">
<pre class="highlight"><code class="language-bash">curl -X POST -H "Content-Type: application/json" \
-H "api-key: ACCESS_TOKEN" \
-d '{"article": {"title": "Title", "body_markdown": "Body", "published": false, "tags": ["discuss", "javascript"]}}' \
https://dev.to/api/articles</code></pre></div>
<p>If <code>published</code> is set to <code>true</code> then the article will be live instantly.</p>
<h3>
<a name="successful-response" href="#successful-response" class="anchor">
</a>
Successful response
</h3>
<p>In case of a successful response (HTTP 201), the full article resource will be returned to the client:</p>
<div class="highlight"><pre class="highlight plaintext"><code class="language-json">{
"type_of": "article",
"id": 110878,
"title": "Title",
"description": "Body\n\n...",
"cover_image": null,
"readable_publish_date": null,
"social_image": "http://dev.to/social_previews/article/110878.png",
"tag_list": "discuss, javascript",
"tags": [
"discuss",
"javascript"
],
"slug": "title-2bp3-temp-slug-5875367",
"path": "/rhymes/title-2bp3-temp-slug-5875367",
"url": "https://dev.to/rhymes/title-2bp3-temp-slug-5875367",
"canonical_url": "https://dev.to/rhymes/title-2bp3-temp-slug-5875367",
"comments_count": 0,
"positive_reactions_count": 0,
"created_at": "2019-05-21T13:34:26Z",
"edited_at": null,
"crossposted_at": null,
"published_at": null,
"last_comment_at": "2017-01-01T05:00:00Z",
"body_html": "&lt;p&gt;Body&lt;/p&gt;\n\n",
"ltag_style": [],
"ltag_script": [],
"user": {
"name": "rhymes",
"username": "rhymes",
"twitter_username": "rhymes_",
"github_username": "rhymes",
"website_url": null,
"profile_image": "https://res.cloudinary.com/practicaldev/image/fetch/s--qhCNe-v6--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://thepracticaldev.s3.amazonaws.com/uploads/user/profile_image/2693/146201.jpeg",
"profile_image_90": "https://res.cloudinary.com/practicaldev/image/fetch/s--IQPhTQnb--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://thepracticaldev.s3.amazonaws.com/uploads/user/profile_image/2693/146201.jpeg"
}
}</code></pre></div>
<h3>
<a name="create-an-article-on-behalf-an-organisation" href="#create-an-article-on-behalf-an-organisation" class="anchor">
</a>
Create an article on behalf an organisation
</h3>
<p>Just add <code>organization_id: your_org_id</code> to the JSON request:</p>
<div class="highlight"><pre class="highlight plaintext"><code class="language-bash">curl -X POST -H "Content-Type: application/json" \
-H "api-key: ACCESS_TOKEN" \
-d '{"article": {"title": "Title", "body_markdown": "Body", "published": false, "organization_id": 99999}}' \
https://dev.to/api/articles</code></pre></div>
<h3>
<a name="using-the-front-matter" href="#using-the-front-matter" class="anchor">
</a>
Using the front matter
</h3>
<p>You can omit most JSON parameters by using the markdown front matter. So for example, if you have markdown article similar to the following:</p>
<div class="highlight"><pre class="highlight plaintext"><code class="language-markdown">---
title: "A sample article about JavaScript"
published: false
description: "this is a sample article about javascript"
tags: discuss, javascript
series: intro-to-javascript
canonical_url: canonical URL of the article
---
This is an intro about JavaScript</code></pre></div>
<p>you can send it within the <code>body_markdown</code> parameter:</p>
<div class="highlight"><pre class="highlight plaintext"><code class="language-bash">curl -X POST -H "Content-Type: application/json" \
-H "api-key: ACCESS_TOKEN" \
-d '{"article": {"body_markdown": "---\ntitle: A sample article about...\npublished: false\n---\n..."}}' \
https://dev.to/api/articles</code></pre></div>
<p>The front matter will take precedence on other JSON params with the same name.</p>
<h3>
<a name="available-json-parameters" href="#available-json-parameters" class="anchor">
</a>
Available JSON parameters
</h3>
<div class="highlight"><pre class="highlight plaintext"><code>title: The title of an article (string, optional)
description: Description of the article (string, optional)
body_markdown: The Markdown body, with or without a front matter (string, required)
published: True if the article should be published right away, defaults to false (boolean, optional)
tags: A list of tags for the article (array, optional)
series: The name of the series the article should be published within (string, optional)
organization_id: Your organization's ID, if you wish to create an article under an organization (string, optional)
main_image: URL of the image to use as the cover (string, optional)
canonical_url: canonical URL of the article (string, optional)</code></pre></div>
<h3>
<a name="error-responses" href="#error-responses" class="anchor">
</a>
Error responses
</h3>
<p><strong>Unauthorized</strong></p>
<div class="highlight"><pre class="highlight plaintext"><code class="language-json">{
"error": "unauthorized",
"status": 401
}</code></pre></div>
<p><strong>Parameter validation error</strong></p>
<div class="highlight"><pre class="highlight plaintext"><code class="language-json">{
"error": "Validation failed: Title can't be blank",
"status": 422
}</code></pre></div>
<h2>
<a name="updating-an-article-through-the-api" href="#updating-an-article-through-the-api" class="anchor">
</a>
Updating an article through the API
</h2>
<p>Here is an example of how to update an article:</p>
<div class="highlight"><pre class="highlight plaintext"><code class="language-shell">curl -X PUT -H "Content-Type: application/json" \
-H "api-key: ACCESS_TOKEN" \
-d '{"article": {"title": "New Title"}}' \
https://dev.to/api/articles/ARTICLE_ID</code></pre></div>
<h3>
<a name="successful-response" href="#successful-response" class="anchor">
</a>
Successful response
</h3>
<p>In case of a successful response (HTTP 200), the full article resource will be returned to the client:</p>
<div class="highlight"><pre class="highlight plaintext"><code class="language-json">{
"type_of": "article",
"id": 110878,
"title": "New Title",
"description": "Body\n\n...",
"cover_image": null,
"readable_publish_date": null,
"social_image": "http://dev.to/social_previews/article/110878.png",
"tag_list": "discuss, javascript",
"tags": [
"discuss",
"javascript"
],
"slug": "title-2bp3-temp-slug-5875367",
"path": "/rhymes/title-2bp3-temp-slug-5875367",
"url": "https://dev.to/rhymes/title-2bp3-temp-slug-5875367",
"canonical_url": "https://dev.to/rhymes/title-2bp3-temp-slug-5875367",
"comments_count": 0,
"positive_reactions_count": 0,
"created_at": "2019-05-21T13:34:26Z",
"edited_at": null,
"crossposted_at": null,
"published_at": null,
"last_comment_at": "2017-01-01T05:00:00Z",
"body_html": "&lt;p&gt;Body&lt;/p&gt;\n\n",
"ltag_style": [],
"ltag_script": [],
"user": {
"name": "rhymes",
"username": "rhymes",
"twitter_username": "rhymes_",
"github_username": "rhymes",
"website_url": null,
"profile_image": "https://res.cloudinary.com/practicaldev/image/fetch/s--qhCNe-v6--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://thepracticaldev.s3.amazonaws.com/uploads/user/profile_image/2693/146201.jpeg",
"profile_image_90": "https://res.cloudinary.com/practicaldev/image/fetch/s--IQPhTQnb--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://thepracticaldev.s3.amazonaws.com/uploads/user/profile_image/2693/146201.jpeg"
}
}</code></pre></div>
<h3>
<a name="notes-about-the-front-matter-during-an-update" href="#notes-about-the-front-matter-during-an-update" class="anchor">
</a>
Notes about the front matter during an update
</h3>
<p>Keep in mind that if the original article was created with the front matter in the markdown then that front matter will still take precedence over the partial updates. So if you need to change parameters present in the front matter, you should send the whole edited body (with the front matter parameter changed) in the <code>body_markdown</code> parameter.</p>
<h3>
<a name="available-json-parameters" href="#available-json-parameters" class="anchor">
</a>
Available JSON parameters
</h3>
<div class="highlight"><pre class="highlight plaintext"><code>title: The title of an article (optional)
description: Description of the article (optional)
body_markdown: The Markdown body, with or without a front matter (required)
published: True if the article should be published right away, defaults to false (optional)
series: The name of the series the article should be published within (optional)
organization_id: Your organization's ID, if you wish to create an article under an organization (string, optional)
main_image: URL of the image to use as the cover (optional)
canonical_url: canonical URL of the article (optional)</code></pre></div>
<h3>
<a name="error-responses" href="#error-responses" class="anchor">
</a>
Error responses
</h3>
<p><strong>Unauthorized</strong></p>
<div class="highlight"><pre class="highlight plaintext"><code class="language-json">{
"error": "unauthorized",
"status": 401
}</code></pre></div>
<p><strong>Parameter validation error</strong></p>
<div class="highlight"><pre class="highlight plaintext"><code class="language-json">{
"error": "Validation failed: Title can't be blank",
"status": 422
}</code></pre></div>
<h2>
<a name="rate-limiting" href="#rate-limiting" class="anchor">
</a>
Rate limiting
</h2>
<p>There is a limit of 10 articles created each 30 seconds by the same user. </p>
<p>There are no limits on the updates.</p>
<h2>
<a name="additional-resources" href="#additional-resources" class="anchor">
</a>
Additional resources
</h2>
<ul>
<li><a href="https://github.com/thepracticaldev/dev.to/blob/master/spec/requests/api/v0/articles_spec.rb">Rails tests for the Articles API</a></li>
</ul>
</div>
</div>
</div>

View file

@ -246,7 +246,7 @@ Rails.application.routes.draw do
# You can have the root of your site routed with "root
get "/about" => "pages#about"
get "/api", to: "pages#api"
get "/api", to: redirect("https://docs.dev.to/api")
get "/privacy" => "pages#privacy"
get "/terms" => "pages#terms"
get "/contact" => "pages#contact"

View file

@ -17,6 +17,11 @@
"title": "Ruby doc",
"href": "http://docs.dev.to/ruby-doc",
"target": "_blank"
},
{
"title": "API doc",
"href": "http://docs.dev.to/api",
"target": "_blank"
}
]
}

View file

@ -1,9 +1,17 @@
.PHONY: build
build: ruby-doc gitdocs
build: api-doc ruby-doc gitdocs
api-doc:
# docs/.static/api will let gitdocs serve the API doc as static content
cd .. && \
npm install -g redoc-cli@latest && \
redoc-cli bundle docs/api_v0.yml --options.suppressWarnings --options.pathInMiddlePanel --options.jsonSampleExpandLevel=all --options.menuToggle -t docs/api_template.hbs && \
mkdir -p docs/.static/api/ && \
mv redoc-static.html docs/.static/api/index.html
ruby-doc:
# docs/.static/ruby-doc will let gitdocs serve the Ruby doc as static content
# docs/.static/ruby-doc will let gitdocs serve the Ruby doc as static content
cd .. && yard doc -o docs/.static/ruby-doc
gitdocs:

33
docs/api_template.hbs Normal file
View file

@ -0,0 +1,33 @@
{{! This template is used by ReDoc to render the final API doc }}
{{! It is written in HandlerbarsJS syntax }}
{{! Please refer to https://github.com/Redocly/redoc/blob/master/cli/README.md }}
{{! and https://github.com/Redocly/redoc/blob/master/cli/template.hbs }}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf8" />
<title>DEV API (beta)</title>
<meta name="description" content="DEV REST API documentation">
<meta property="og:type" content="article" />
<meta property="og:url" content="https://docs.dev.to/api" />
<meta property="og:title" content="DEV API (beta)" />
<!-- needed for adaptive design -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
padding: 0;
margin: 0;
}
</style>
{{{redocHead}}}
<link href="https://fonts.googleapis.com/css?family=Montserrat:300,400,700|Roboto:300,400,700" rel="stylesheet">
</head>
<body>
{{{redocHTML}}}
</body>
</html>

808
docs/api_v0.yml Normal file
View file

@ -0,0 +1,808 @@
openapi: '3.0.2'
info:
title: DEV API
description: Access DEV articles, comments and other resources via API
version: '0.5.0'
termsOfService: https://dev.to/terms
contact:
name: DEV Team
url: https://dev.to/contact
email: yo@dev.to
x-logo: # https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md#x-logo
url: https://practicaldev-herokuapp-com.freetls.fastly.net/assets/devlogo-pwa-128-1a9016906487b8ba17f2d3d3c28f9d1cb3ae9b45d685cc2711e20f6b3c6730df.png
backgroundColor: "#000"
altText: "DEV logo"
servers:
- url: https://dev.to/api
description: Production server
components:
securitySchemes:
api_key:
type: apiKey
description: |
API Key authentication.
### Getting an API key
Authentication for the Articles API requires an API key (DEV API access token).
To obtain one, please follow these steps:
- visit https://dev.to/settings/account
- in the "DEV API Access Tokens" section create a new token by adding a
description and clicking on "Generate Token"
![obtain a DEV API access token](https://res.cloudinary.com/practicaldev/image/fetch/s--F4Ya4WgC--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/4zm0cbutj9o9eau0jm9t.png)
- You'll see the newly generated token in the same view
![generated DEV API access token](https://res.cloudinary.com/practicaldev/image/fetch/s--kp5L6-9G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://thepracticaldev.s3.amazonaws.com/i/ra01mo6fp1es261db2ho.png)
name: api-key
in: header
schemas:
APIError:
type: object
required:
- error
- status
properties:
error:
type: string
status:
type: integer
format: int32
ArticleIndex:
type: object
required:
- type_of
- id
- title
- description
- cover_image
- published_at
- tag_list
- slug
- path
- url
- canonical_url
- positive_reactions_count
- published_timestamp
- user
properties:
type_of:
type: string
id:
type: integer
format: int32
title:
type: string
description:
type: string
cover_image:
type: string
format: url
published_at:
type: string
format: date-time
tag_list:
type: array
items:
type: string
slug:
type: string
path:
type: string
format: path
url:
type: string
format: url
canonical_url:
type: string
format: url
positive_reactions_count:
type: integer
format: int32
published_timestamp:
description: Crossposting or published date time
type: string
format: date-time
user:
type: object
$ref: '#/components/schemas/User'
organization:
description: Organization the article belongs to
type: object
properties:
name:
type: string
username:
type: string
slug:
type: string
profile_image:
description: Profile image (640x640)
type: string
format: url
profile_image_90:
description: Profile image (90x90)
type: string
format: url
flare_tag:
description: Flare tag of the article
type: object
properties:
name:
type: string
bg_color_hex:
description: Background color (hexadecimal)
type: string
text_color_hex:
description: Text color (hexadecimal)
type: string
ArticleShow:
type: object
required:
- type_of
- id
- title
- description
- cover_image
- readable_publish_date
- social_image
- tag_list
- tags
- slug
- path
- url
- canonical_url
- comments_count
- positive_reactions_count
- created_at
- edited_at
- crossposted_at
- published_at
- last_comment_at
- body_html
- body_markdown
- user
properties:
type_of:
type: string
id:
type: integer
format: int32
title:
type: string
description:
type: string
cover_image:
type: string
format: url
readable_publish_date:
type: string
social_image:
type: string
format: url
tag_list:
type: string
tags:
type: array
items:
type: string
slug:
type: string
path:
type: string
format: path
url:
type: string
format: url
canonical_url:
type: string
format: url
comments_count:
type: integer
format: int32
positive_reactions_count:
type: integer
format: int32
created_at:
type: string
format: date-time
edited_at:
type: string
format: date-time
crossposted_at:
type: string
format: date-time
published_at:
type: string
format: date-time
last_comment_at:
type: string
format: date-time
body_html:
type: string
body_markdown:
type: string
user:
type: object
$ref: '#/components/schemas/User'
ArticleCreate:
type: object
properties:
article:
type: object
required:
- title
properties:
title:
type: string
body_markdown:
description: |
The body of the article.
It can contain an optional front matter. For example
```markdown
---
title: Hello, World!
published: true
tags: discuss, help
date: 20190701T10:00Z
series: Hello series
canonical_url: https://example.com/blog/hello
cover_image: article_published_cover_image
---
```
`date`, `series` and `canonical_url` are optional.
`date` is the publication date-time
`series` is the name of the series the article belongs to
`canonical_url` is the canonical URL of the article
`cover_image` is the main image of the article
*If the markdown contains a front matter, it will take precedence
on the equivalent params given in the JSON payload.*
type: string
published:
description: |
True to create a published article, false otherwise. Defaults to false
type: boolean
series:
description: |
Article series name.
All articles belonging to the same series need to have the same name
in this parameter.
type: string
main_image:
type: string
format: url
canonical_url:
type: string
format: url
description:
type: string
tags:
type: array
items:
type: string
organization_id:
description: |
Only users belonging to an organization can assign the article to it
type: integer
format: int32
ArticleUpdate:
type: object
properties:
article:
type: object
properties:
title:
type: string
body_markdown:
description: |
The body of the article.
It can contain an optional front matter. For example
```markdown
---
title: Hello, World!
published: true
tags: discuss, help
date: 20190701T10:00Z
series: Hello series
canonical_url: https://example.com/blog/hello
cover_image: article_published_cover_image
---
```
`date`, `series` and `canonical_url` are optional.
`date` is the publication date-time
`series` is the name of the series the article belongs to
`canonical_url` is the canonical URL of the article
`cover_image` is the main image of the article
*If the markdown contains a front matter, it will take precedence
on the equivalent params given in the JSON payload.*
type: string
published:
description: |
True to create a published article, false otherwise. Defaults to false
type: boolean
series:
description: |
Article series name.
All articles belonging to the same series need to have the same name
in this parameter.
To remove an article from a series, the `null` value can be used.
type: string
main_image:
type: string
format: url
canonical_url:
type: string
format: url
description:
type: string
tags:
type: array
items:
type: string
organization_id:
description: |
Only users belonging to an organization can assign the article to it
type: integer
format: int32
User:
type: object
properties:
name:
type: string
username:
type: string
twitter_username:
type: string
github_username:
type: string
website_url:
type: string
format: url
profile_image:
description: Profile image (640x640)
type: string
profile_image_90:
description: Profile image (90x90)
type: string
examples:
ErrorNotFound:
value:
error: not found
status: 404
ErrorUnauthorized:
value:
error: unauthorized
status: 401
ArticleShow:
value:
type_of: article
id: 150589
title: 'Byte Sized Episode 2: The Creation of Graph Theory '
description: The full story of Leonhard Euler and the creation of this fundamental
computer science principle, delivered in a few minutes.
cover_image: https://res.cloudinary.com/practicaldev/image/fetch/s--qgutBUrH--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://thepracticaldev.s3.amazonaws.com/i/88e62fzblbluz1dm7xjf.png
readable_publish_date: Aug 1
social_image: https://res.cloudinary.com/practicaldev/image/fetch/s--6wSHHfwd--/c_imagga_scale,f_auto,fl_progressive,h_500,q_auto,w_1000/https://thepracticaldev.s3.amazonaws.com/i/88e62fzblbluz1dm7xjf.png
tag_list: computerscience, graphtheory, bytesized, history
tags:
- computerscience
- graphtheory
- bytesized
- history
slug: byte-sized-episode-2-the-creation-of-graph-theory-34g1
path: "/bytesized/byte-sized-episode-2-the-creation-of-graph-theory-34g1"
url: https://dev.to/bytesized/byte-sized-episode-2-the-creation-of-graph-theory-34g1
canonical_url: https://dev.to/bytesized/byte-sized-episode-2-the-creation-of-graph-theory-34g1
comments_count: 19
positive_reactions_count: 276
created_at: '2019-07-31T11:15:06Z'
edited_at:
crossposted_at:
published_at: '2019-08-01T15:47:54Z'
last_comment_at: '2019-08-06T09:12:45Z'
body_html: |+
<p>Today's episode of Byte Sized is about Leonhard Euler and the creation of <a href="https://en.wikipedia.org/wiki/Graph_theory">Graph Theory</a>.</p>
<p>For more about how Graph Theory works, check out this video from BaseCS!</p>...
body_markdown: "---\r\ntitle: Byte Sized Episode 2: The Creation of Graph Theory \r\npublished:
true\r\ndescription: The full story of Leonhard Euler and the creation of this fundamental
computer science principle, delivered in a few minutes.\r\ntags: computerscience,
graphtheory, bytesized, history\r\ncover_image: https://thepracticaldev.s3.amazonaws.com/i/88e62fzblbluz1dm7xjf.png\r\nseries:
Byte Sized Season 1\r\n---\r\n\r\nToday's episode of Byte Sized is about Leonhard
Euler and the creation of [Graph Theory](https://en.wikipedia.org/wiki/Graph_theory).\r\n\r\nFor
more about how Graph Theory works, check out this video from BaseCS!..."
user:
name: Vaidehi Joshi
username: vaidehijoshi
twitter_username: vaidehijoshi
github_username: vaidehijoshi
website_url: http://www.vaidehi.com
profile_image: https://res.cloudinary.com/practicaldev/image/fetch/s--eDGAYAoK--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://thepracticaldev.s3.amazonaws.com/uploads/user/profile_image/2882/K2evUksb.jpg
profile_image_90: https://res.cloudinary.com/practicaldev/image/fetch/s--htZnqMn3--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://thepracticaldev.s3.amazonaws.com/uploads/user/profile_image/2882/K2evUksb.jpg
ArticleCreateTitleBody:
value:
article:
title: Hello, World!
published: true
body_markdown: Hello DEV, this is my first post
tags: discuss, help
series: Hello series
canonical_url: https://example.com/blog/hello
ArticleCreateFrontMatter:
value:
article:
body_markdown: |
---
title: Hello, World!
published: true
tags: discuss, help
date: 20190701T10:00Z
series: Hello series
canonical_url: https://example.com/blog/hello
---
Hello DEV, this is my first post
ArticleCreateOrganization:
value:
article:
title: Hello, World!
published: true
body_markdown: Hello DEV, this is my first post
tags: discuss, help
series: Hello series
canonical_url: https://example.com/blog/hello
organization_id: 1234
paths:
/articles:
get:
summary: Published articles
description: |
This endpoint allows the client to retrieve a list of articles.
"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.
By default it will return featured, published articles ordered
by descending popularity.
Each page will contain `30` articles.
Responses, according to the combination of params, are cached for 24 hours.
tags:
- articles
parameters:
- name: page
in: query
description: |
Pagination page.
This param can be used in conjuction with all other params
(except when asking for fresh and rising articles by themselves).
schema:
type: integer
format: int32
example: 1
- name: tag
in: query
description: |
Adding this parameter will return articles that contain the
requested tag.
This param can be used by itself, with `page` or with `top`.
schema:
type: string
example: discuss
- name: username
in: query
description: |
Adding this parameter will return articles belonging to a User or Organization
ordered by descending `published_at`.
If `state=all` the number of items returned will be `1000` instead of the default `30`.
This param can be used by itself or only with `page` and `state`.
schema:
type: string
example: ben
- name: state
in: query
description: |
Adding this will allow the client to check which articles are fresh or rising.
If `state=fresh` the server will return published fresh articles.
If `state=rising` the server will return published rising articles.
This param can only be used by itself or with `username` if set to `all`.
schema:
type: string
example: ben
- name: top
in: query
description: |
Adding this will allow the client to return the most popular articles
in the last `N` days.
`top` indicates the number of days since publication of the articles
returned.
This param can only be used by itself or with `tag`.
schema:
type: integer
format: int32
example: 2
responses:
200:
description: A list of articles
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/ArticleIndex'
examples:
articles-success:
summary: Successful response
value:
type_of: article
id: 150589
title: 'Byte Sized Episode 2: The Creation of Graph Theory'
description: The full story of Leonhard Euler and the creation of this fundamental
computer science principle, delivered in a few minutes.
cover_image: https://res.cloudinary.com/practicaldev/image/fetch/s--qgutBUrH--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://thepracticaldev.s3.amazonaws.com/i/88e62fzblbluz1dm7xjf.png
published_at: '2019-08-01T15:47:54.000Z'
tag_list:
- computerscience
- graphtheory
- bytesized
- history
slug: byte-sized-episode-2-the-creation-of-graph-theory-34g1
path: "/bytesized/byte-sized-episode-2-the-creation-of-graph-theory-34g1"
url: https://dev.to/bytesized/byte-sized-episode-2-the-creation-of-graph-theory-34g1
canonical_url: https://dev.to/bytesized/byte-sized-episode-2-the-creation-of-graph-theory-34g1
comments_count: 15
positive_reactions_count: 210
published_timestamp: '2019-08-01T15:47:54Z'
user:
name: Vaidehi Joshi
username: vaidehijoshi
twitter_username: vaidehijoshi
github_username: vaidehijoshi
website_url: http://www.vaidehi.com
profile_image: https://res.cloudinary.com/practicaldev/image/fetch/s--eDGAYAoK--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://thepracticaldev.s3.amazonaws.com/uploads/user/profile_image/2882/K2evUksb.jpg
profile_image_90: https://res.cloudinary.com/practicaldev/image/fetch/s--htZnqMn3--/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://thepracticaldev.s3.amazonaws.com/uploads/user/profile_image/2882/K2evUksb.jpg
organization:
name: Byte Sized
username: bytesized
slug: bytesized
profile_image: https://res.cloudinary.com/practicaldev/image/fetch/s--sq0DrZfn--/c_fill,f_auto,fl_progressive,h_640,q_auto,w_640/https://thepracticaldev.s3.amazonaws.com/uploads/organization/profile_image/865/652f7998-32a8-4fd9-85ca-dd697d2a9ee9.png
profile_image_90: https://res.cloudinary.com/practicaldev/image/fetch/s--1Pt_ICL---/c_fill,f_auto,fl_progressive,h_90,q_auto,w_90/https://thepracticaldev.s3.amazonaws.com/uploads/organization/profile_image/865/652f7998-32a8-4fd9-85ca-dd697d2a9ee9.png
x-code-samples: # https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md#x-code-samples
- lang: Shell
label: curl (all articles)
source: |
curl https://dev.to/api/articles
- lang: Shell
label: curl (user's articles)
source: |
curl https://dev.to/api/articles?username=ben
post:
summary: Create a new article
description: |
This endpoint allows the client to create a new article.
"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.
### Rate limiting
There is a limit of 10 articles created each 30 seconds by the same user.
### Additional resources
- [Rails tests for Articles API](https://github.com/thepracticaldev/dev.to/blob/master/spec/requests/api/v0/articles_spec.rb)
tags:
- articles
requestBody:
description: Article to create
content:
application/json:
schema:
$ref: '#/components/schemas/ArticleCreate'
examples:
article-create-title-body:
$ref: '#/components/examples/ArticleCreateTitleBody'
article-create-front-matter:
$ref: '#/components/examples/ArticleCreateFrontMatter'
article-create-organization:
$ref: '#/components/examples/ArticleCreateOrganization'
responses:
201:
description: A newly created article
content:
application/json:
schema:
type: object
$ref: '#/components/schemas/ArticleShow'
examples:
article-success:
$ref: '#/components/examples/ArticleShow'
headers:
Location:
description: The URL of the new article
schema:
type: string
format: url
401:
description: Unauthorized
content:
application/json:
schema:
type: object
$ref: '#/components/schemas/APIError'
examples:
article-unauthorized:
$ref: '#/components/examples/ErrorUnauthorized'
security:
- api_key: []
x-code-samples: # https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md#x-code-samples
- lang: Shell
label: curl
source: |
curl -X POST -H "Content-Type: application/json" \
-H "api-key: ACCESS_TOKEN" \
-d '{"article":{"title":"Title","body_markdown":"Body","published":false,"tags":["discuss", "javascript"]}}' \
https://dev.to/api/articles
- lang: Shell
label: curl (with front matter)
source: |
curl -X POST -H "Content-Type: application/json" \
-H "api-key: ACCESS_TOKEN" \
-d '{"article":{"body_markdown":"---\ntitle:A sample article about...\npublished:false\n---\n..."}}' \
https://dev.to/api/articles
/articles/{id}:
get:
summary: A published article
description: |
This endpoint allows the client to retrieve a single
published article given its `id`.
Responses are cached for 5 minutes.
tags:
- articles
parameters:
- name: id
in: path
required: true
description: Id of the article
schema:
type: integer
format: int32
example: 150589
responses:
200:
description: An article
content:
application/json:
schema:
type: object
$ref: '#/components/schemas/ArticleShow'
examples:
article-success:
$ref: '#/components/examples/ArticleShow'
404:
description: Resource not found
content:
application/json:
schema:
type: object
$ref: '#/components/schemas/APIError'
examples:
article-not-found:
$ref: '#/components/examples/ErrorNotFound'
x-code-samples: # https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md#x-code-samples
- lang: Shell
label: curl
source: |
curl https://dev.to/api/articles/150589
put:
summary: Update an existing article
description: |
This endpoint allows the client to updated an existing article.
"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.
### Rate limiting
There are no limits on the amount of updates.
### Additional resources
- [Rails tests for Articles API](https://github.com/thepracticaldev/dev.to/blob/master/spec/requests/api/v0/articles_spec.rb)
tags:
- articles
parameters:
- name: id
in: path
required: true
description: Id of the article
schema:
type: integer
format: int32
example: 150589
requestBody:
description: |
Article params to update.
*Note: if the article contains a front matter in its body, its front
matter properties will still take precedence over any JSON equivalent
params, which means that the full body_markdown with the modified
front matter params needs to be provided for an update to be successful*
content:
application/json:
schema:
$ref: '#/components/schemas/ArticleUpdate'
examples:
article-update-title-body:
$ref: '#/components/examples/ArticleCreateTitleBody'
article-update-front-matter:
$ref: '#/components/examples/ArticleCreateFrontMatter'
article-update-organization:
$ref: '#/components/examples/ArticleCreateOrganization'
responses:
200:
description: The updated article
content:
application/json:
schema:
type: object
$ref: '#/components/schemas/ArticleShow'
examples:
article-success:
$ref: '#/components/examples/ArticleShow'
401:
description: Unauthorized
content:
application/json:
schema:
type: object
$ref: '#/components/schemas/APIError'
examples:
article-unauthorized:
$ref: '#/components/examples/ErrorUnauthorized'
security:
- api_key: []
x-code-samples: # https://github.com/Redocly/redoc/blob/master/docs/redoc-vendor-extensions.md#x-code-samples
- lang: Shell
label: curl
source: |
curl -X PUT -H "Content-Type: application/json" \
-H "api-key: ACCESS_TOKEN" \
-d '{"article":{"title":"Title"}' \
https://dev.to/api/articles/{id}
security:
- api_key: []

29
docs/contributing_api.md Normal file
View file

@ -0,0 +1,29 @@
---
title: Contributing to API spec docs
---
# Contributing to API spec docs
The API docs follow the [OpenAPI 3 specification](http://spec.openapis.org/oas/v3.0.2).
## Where The Docs Are Located
We auto generate the documentation from `api_v0.yml` within the `/docs` directory.
We use [ReDoc](https://github.com/Redocly/redoc) to turn the OpenAPI 3 format into a readable and searchable HTML documentation.
## Running and editing the Docs Locally
If you want to browse the documentation locally you can use:
```shell
yarn api-docs:serve
```
This will let you browse the auto generated version of the doc locally and it will reload the doc after every modification
of the spec file.
If you have Visual Studio Code we suggest you install the following extensions that enable validation and navigation within
the spec file:
- [OpenAPI (Swagger) editor](https://marketplace.visualstudio.com/items?itemName=42Crunch.vscode-openapi)
- [openapi-designer live preview](https://marketplace.visualstudio.com/items?itemName=philosowaffle.openapi-designer)

View file

@ -8,6 +8,7 @@ items:
- design
- tests
- contributing.md
- contributing_api.md
- faqs.md
- licensing.md
- self-hosting.md

View file

@ -18,6 +18,12 @@
[[redirects]]
from = "http://docs.dev.to/ruby-doc/"
to = "https://docs.dev.to/ruby-doc/index.html"
[[redirects]]
from = "http://docs.dev.to/api"
to = "https://docs.dev.to/api/index.html"
[[redirects]]
from = "http://docs.dev.to/api/"
to = "https://docs.dev.to/api/index.html"
[[redirects]]
from = "http://docs.dev.to/*"
to = "https://docs.dev.to/:splat"

View file

@ -12,7 +12,8 @@
"test": "jest app/javascript/ --coverage",
"test:watch": "jest app/javascript/ --watch",
"storybook": "start-storybook -p 6006 -c app/javascript/.storybook -s app/javascript/.storybook/assets",
"build-storybook": "build-storybook -c app/javascript/.storybook -s app/javascript/.storybook/assets"
"build-storybook": "build-storybook -c app/javascript/.storybook -s app/javascript/.storybook/assets",
"api-docs:serve": "yarn redoc-cli serve docs/api_v0.yml --options.pathInMiddlePanel --options.jsonSampleExpandLevel=all --options.menuToggle -t docs/api_template.hbs --watch"
},
"husky": {
"hooks": {
@ -82,6 +83,7 @@
"preact-render-spy": "1.3.0",
"preact-render-to-json": "^3.6.6",
"prettier": "^1.18.2",
"redoc-cli": "0.8.6",
"webpack-dev-server": "^2.11.3"
},
"dependencies": {

View file

@ -65,6 +65,16 @@ RSpec.describe "Api::V0::Articles", type: :request do
get api_articles_path(tag: tag.name)
expect(JSON.parse(response.body).size).to eq(0)
end
it "returns flare tag in the response" do
create(:article, featured: true, tags: "discuss")
get api_articles_path
response_article = JSON.parse(response.body)[0]
expect(response_article["flare_tag"]).to be_present
expect(response_article["flare_tag"].keys).to eq(%w[name bg_color_hex text_color_hex])
expect(response_article["flare_tag"]["name"]).to eq("discuss")
end
end
describe "GET /api/articles/:id" do
@ -453,7 +463,7 @@ RSpec.describe "Api::V0::Articles", type: :request do
expect(response).to have_http_status(:ok)
end
it "does not update title if only given a title" do
it "does not update title if only given a title because the article has a front matter" do
put_article(title: Faker::Book.title + rand(100).to_s)
expect(response).to have_http_status(:ok)
expect(article.reload.title).to eq(article.title)

View file

@ -26,9 +26,9 @@ RSpec.describe "Pages", type: :request do
end
describe "GET /api" do
it "has proper headline" do
it "redirects to the API docs" do
get "/api"
expect(response.body).to include("DEV Articles API")
expect(response.body).to redirect_to("https://docs.dev.to/api")
end
end

730
yarn.lock

File diff suppressed because it is too large Load diff