Skip to content

Latest commit

 

History

History
348 lines (264 loc) · 9.58 KB

README.md

File metadata and controls

348 lines (264 loc) · 9.58 KB

Sirius import article

Step-by-step tutorial on how to import an article into Sirius app with the GraphQL API

Summary

Playground

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:prod permission.

Source article

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.json

Data correspondence

Correspondence 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

Required data

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).

Get the "Article" layout id

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

Get the "Factuel" editorial type id

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

Edition type id

The editorialTypeId is : bG9jYWw6RWRpdG9yaWFsVHlwZToyMQ

we could use the editorialType query to retrieve the same id.

Linked resources

Source creation

We use createSource mutation to create "dev.to" source.

mutation CreateSource {
  createSource(input: { key: "dev_to", name: "dev.to" }) {
    id
  }
}

The sourceId is: bG9jYWw6U291cmNlOjIx

Tags creation

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

Author creation

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

Images import

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
  }
}

HTML parsing

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))
}

Article blocks

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 articleBlocks

We 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.