Step-by-step tutorial on how to import an article into Sirius app with the GraphQL API
GraphQL queries and mutations will be executed in Sirius GraphQL playground:
https://*.sirius.press/api/public/graphql
It requires to be logged in with a Sirius account that has
admin:access:prodpermission.
In this tutorial, we will import this article from DEV.to API.
We curl the API and store the result of article.json file.
curl https://dev.to/api/articles/768225 > article.jsonCorrespondence between article data and Sirius fields.
| User data | Sirius author fields |
|---|---|
| name | name |
| profile_image | avatarId |
Tip: If you would like to add some extra fields (e.g.
twitter_username), you are able to create a custom field for authors from Sirius Admin dashboard.
| Image data | Sirius image fields |
|---|---|
| cover_image | file or url |
| Article data | Sirius article fields |
|---|---|
| title | title |
| description | chapo |
| tags | tagIds |
| body_html | blocks |
| user | signatures |
| url | slug |
| published_at | firstPublishedAt |
| social_image | featureImage |
Some data from DEV.to API can't be imported in Sirius API.
Show ignored article data list
| Ignored article data | |||
|---|---|---|---|
| type_of | id | readable_publish_date | slug |
| path | comments_count | public_reactions_count | collection_id |
| published_timestamp | positive_reactions_count | last_comment_at | canonical_url |
| reading_time_minutes | tag_list | body_markdown | url |
| created_at | edited_at | crossposted_at |
| Ignored user data | |||
|---|---|---|---|
| twitter_username | username | github_username | website_url |
| profile_image_90 |
| Ignored flare_tag data | ||
|---|---|---|
| name | bg_color_hex | text_color_hex |
To create an article on Sirius API we use the createArticle mutation with a createArticleInput param. (browse "Docs" on your graphQL playground)
The minimal required fields to create an article are article layout id (layoutId) and editorial type id (editorialTypeId).
Execute this articleLayouts query in the Playground to get "Article" layout id.
Here we search the "article" layout
query Layout {
articleLayout(key: "article") {
id
name
}
}The return is :
{
"data": {
"articleLayout": {
"id": "bG9jYWw6TGF5b3V0OjE",
"name": "Article"
}
}
}The "article" layoutId : bG9jYWw6TGF5b3V0OjE
There is an editorial types admin page where we can find the "Factuel" id.
- Go to editorial types admin page :
/cms-client/admin/article/editorial-types - Click on "Factuel".
- At the bottom end of the page copy the Global ID. This global id is the research
editorialTypeId
The editorialTypeId is : bG9jYWw6RWRpdG9yaWFsVHlwZToyMQ
we could use the
editorialTypequery to retrieve the same id.
We use createSource mutation to create "dev.to" source.
mutation CreateSource {
createSource(input: { key: "dev_to", name: "dev.to" }) {
id
}
}The sourceId is: bG9jYWw6U291cmNlOjIx
Execute createRubric mutations to create the Tags :
mutation CreateRubric {
createRubric(input: { key: "javascript", name: "javascript", main: true }) {
id
name
}
}for each tag, for this example: "react", "showdev", "discuss"
Tags ids are :
- "javascript":
bG9jYWw6VGFnOjM5MTYy - "react":
bG9jYWw6VGFnOjM5MTYz - "showdev":
bG9jYWw6VGFnOjM5MTYx - "discuss":
bG9jYWw6VGFnOjM5MTY0
Execute the createAuthor mutation to create a new author named "DHANUSH N".
mutation CreateAuthor {
createAuthor(input: { name: "DHANUSH N" }) {
id
name
}
}The return authorId is: bG9jYWw6QXV0aG9yOjQ
The cover image is an "illustration". Execute the following query to find "illustration" mediaTypeId
query MediaType {
mediaType(key: "illustration") {
id
name
}
}The mediaTypeId are :
- "Illustration":
bG9jYWw6TWVkaWE6Mw - "Photo":
bG9jYWw6TWVkaWE6Mg
ps. It could have been found in the media admin page : /admin/medias
We now need to import all images :
// src/index.js
import { uploadImageFromUrl } from "./src/image-upload"
import article from "./src/fixtures/article" assert { type: "json" }
import { getImageUrls } from "./src/get-image-urls"
const ILLUSTRATION_MEDIA_TYPE_ID = "bG9jYWw6TWVkaWE6Mw"
const PHOTO_MEDIA_TYPE_ID = "bG9jYWw6TWVkaWE6Mg"
async function importImages() {
try {
await uploadImageFromUrl({
log: "cover",
url: article.cover_image,
// "Illustration" media type id
mediaTypeId: ILLUSTRATION_MEDIA_TYPE_ID,
})
await uploadImageFromUrl({
log: "user profile",
url: article.user.profile_image_90,
// "Photo" media type id
mediaTypeId: PHOTO_MEDIA_TYPE_ID,
})
const hast = htmlToHast(article.body_html)
const imageIds = await importContentImages(hast)
} catch (error) {
if (error.response?.data?.errors?.length) {
console.log(JSON.stringify(error.response.data.errors, null, 2))
} else {
console.error("Upload image error", error)
}
}
}You can also use createOrUpdateImageFromUrl mutation instead of createOrUpdateImage to upload images by its URL :
mutation {
createOrUpdateImageFromUrl(
input: {
url: "https://res.cloudinary.com/practicaldev/image/fetch/s--vvaIehy5--/c_imagga_scale,f_auto,fl_progressive,h_420,q_auto,w_1000/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3fg8ztmywdcuossl75ch.png"
mediaTypeId: "bG9jYWw6TWVkaWE6Mw"
#...
}
) {
id
}
}In the previous article, we use the htmlToHast() to parse the article HTML body. In this parsing we use "unified" and "rehype-parse" libraries. Implementation details are in the html-to-hast file.
// src/html-to-hast.js
import { unified } from "unified"
import parse from "rehype-parse"
function sanitizeHtmlString(html) {
return html.replace(/\n/g, "").replace(/<(\/)?code>/g, "")
}
export function htmlToHast(html) {
return unified()
.data("settings", { fragment: true })
.use(parse, { emitParseErrors: false, duplicateAttribute: false })
.parse(sanitizeHtmlString(html))
}We transform the article content ("article.body_html") in Sirius BlockInput:
// src/create-article.js
const articleBlocks = []
visit(hast, "element", (node) => {
if (node.tagName !== "p") return
const { blocks } = parseNode(node, imageIds)
articleBlocks.push(...blocks)
})
return articleBlocksWe execute the ArticleCreation mutation with the article data:
// src/create-article.js
export async function createArticle(article, blocks) {
console.log("Importing article...")
const { data } = await axios.post(
config.api_endpoint,
{
query: `mutation CreateArticle($createArticleInput: CreateArticleInput!) {
createArticle(input: $createArticleInput) {
id
}
}`,
variables: {
createArticleInput: {
layoutId: "bG9jYWw6TGF5b3V0OjE",
editorialTypeId: "bG9jYWw6RWRpdG9yaWFsVHlwZToyMQ",
sourcings: [{ sourceId: "bG9jYWw6U291cmNlOjIx" }],
title: article.title,
chapo: article.description,
tagIds: [
"bG9jYWw6VGFnOjM5MTYy",
"bG9jYWw6VGFnOjM5MTYz",
"bG9jYWw6VGFnOjM5MTYx",
"bG9jYWw6VGFnOjM5MTY0",
],
signatures: [{ authorId: "bG9jYWw6QXV0aG9yOjQ" }],
blocks,
},
},
},
{ headers: { Authorization: `Bearer ${config.bearer_token}` } }
)
console.log(`- Article created with id: "${data.data.createArticle.id}"`)
return createdArticle
}The full import article code is in index.js file.
