const path = require(`path`) const { createFilePath } = require(`gatsby-source-filesystem`) const fixtures = require("../../docs/api/fixtures.json") const convertToKebabCase = string => { return string .replace(/\s+/g, "-") .replace("'", "") .replace(".", "") .replace('"', "") .toLowerCase() } const createCustomNode = ({ name, node, createNode }) => { const tags = node.tags const nodePaths = Object.entries(node.paths).map(([path, values]) => { return { name: path, methods: Object.entries(values).map(([method, values]) => { let requestBodyValues = undefined if (values.requestBody && values.requestBody.content) { requestBodyValues = values.requestBody.content["application/json"].schema } return { method: method, ...values, requestBody: { required: requestBodyValues ? requestBodyValues.required : undefined, type: requestBodyValues ? requestBodyValues.type : undefined, properties: Object.entries( requestBodyValues ? requestBodyValues.properties : {} ).map(([property, values]) => { let ref = null let component_id = null let nestedModelNode = null const { items, anyOf, oneOf, ...rest } = values if (items || anyOf || oneOf) { if (items && items.anyOf) { component_id = items.anyOf[0]["$ref"].substring( items.anyOf[0]["$ref"].lastIndexOf("/") + 1 ) ref = node.components.schemas[component_id] } else if (items) { const refPath = items["$ref"] if (refPath) { component_id = refPath.substring( items["$ref"].lastIndexOf("/") + 1 ) ref = node.components.schemas[component_id] } } else if (anyOf) { component_id = anyOf[0]["$ref"].substring( anyOf[0]["$ref"].lastIndexOf("/") + 1 ) ref = node.components.schemas[component_id] } else if (oneOf) { component_id = oneOf[0]["$ref"].substring( oneOf[0]["$ref"].lastIndexOf("/") + 1 ) ref = node.components.schemas[component_id] } } if (ref) { const { properties, ...rest } = ref let nestedModelProperties = [] if (properties) { Object.entries(properties).map(([key, values]) => { nestedModelProperties.push({ property: key, ...values, }) }) nestedModelNode = { properties: nestedModelProperties, ...rest, } } } return { property: property, nestedModel: nestedModelNode, ...rest, } }), }, responses: Object.entries(values.responses).map( ([response, values]) => { let properties = undefined if (values && values.content) { properties = values.content["application/json"].schema.properties } return { status: response, content: properties ? Object.entries(properties).map(([property, values]) => { const toSet = {} let concept if (values.items && values.items["$ref"]) { const [, ...path] = values.items["$ref"].split("/") concept = path[path.length - 1] if (values.type === "array") { if (concept in fixtures.resources) { //make the array key plural let prop if (property.slice(-1) !== "s") { prop = property + "s" } else { prop = property } toSet[prop] = [fixtures.resources[concept]] } } else if (concept in fixtures.resources) { toSet[property] = fixtures.resources[concept] } } else { if (fixtures.resources[property]) { toSet[property] = fixtures.resources[property] } else if (values["$ref"]) { const [, ...path] = values["$ref"].split("/") toSet[property] = fixtures.resources[path[path.length - 1]] } } const json = Object.keys(toSet).length > 0 ? JSON.stringify(toSet, undefined, 2) : null return { property: property, json: json, ...values, } }) : null, description: values.description, } } ), } }), } }) const nodeSections = nodePaths.reduce((acc, current) => { // Not bulleproof and kind of naive const section = current.methods.find(method => method.tags).tags[0] if (!section) { return acc } const existingSection = acc.find(s => s.section.section_name === section) //Because section_name does not always equal the resourceId const tag = tags.find( tag => tag.name.toLowerCase() === section.toLowerCase() ) let resourceId if (tag) resourceId = tag["x-resourceId"] const schema = node.components.schemas[section.toLowerCase()] || node.components.schemas[resourceId] || null //Create a schemaNode so we can access descriptions and attributes of objects let schemaNode if (schema) { let props = [] Object.entries(schema.properties).map(([key, values]) => { let ref = null let component_id = null let nestedModelNode = null let { items, anyOf, oneOf, ...rest } = values if (items || anyOf || oneOf) { if (items && items.anyOf) { component_id = items.anyOf[0]["$ref"].substring( items.anyOf[0]["$ref"].lastIndexOf("/") + 1 ) ref = node.components.schemas[component_id] } else if (items) { const refPath = items["$ref"] if (refPath) { component_id = refPath.substring( items["$ref"].lastIndexOf("/") + 1 ) ref = node.components.schemas[component_id] } } else if (anyOf) { component_id = anyOf[0]["$ref"].substring( anyOf[0]["$ref"].lastIndexOf("/") + 1 ) ref = node.components.schemas[component_id] } if (ref) { const { properties, ...rest } = ref let nestedModelProperties = [] if (properties) { Object.entries(properties).map(([key, values]) => { nestedModelProperties.push({ property: key, ...values, }) }) nestedModelNode = { properties: nestedModelProperties, ...rest, } } } } props.push({ property: key, nestedModel: nestedModelNode, ...rest, }) }) let object = fixtures.resources[resourceId] || null if (object) object = JSON.stringify(object, undefined, 2) schemaNode = { object: object, description: schema.description, properties: props, } } /** We want the query to return the following: * * sections [ * section: { * section_name: Orders * paths: [] * } * ] * * The reason that we wrap it in a section is so the query returns Section type, the alternative is * sections [ * Order: {...}, * Customer: {...} * ... * ] * * Which isn't structured as a reuseable type */ if (!existingSection) { acc.push({ section: { section_name: section, paths: [{ ...current }], schema: schemaNode, }, }) } else { existingSection.section.paths.push({ ...current }) } //acc[section].section[current.name] = { ...current } return acc }, []) const result = { name: name, paths: nodePaths, sections: nodeSections, rawNode: node, // required fields id: name, parent: null, // or null if it's a source node without a parent children: [], internal: { type: name, contentDigest: `store-api-${node.internal.contentDigest}`, mediaType: node.internal.mediaType, // optional content: node.internal.content, // optional description: `A cleaned version of file-system-api json files`, // optional }, } createNode(result) } exports.onCreateNode = ({ node, getNode, actions }) => { const { createNodeField, createNode } = actions if (node.internal.type === `MarkdownRemark`) { const slug = createFilePath({ node, getNode }) createNodeField({ node, name: `slug`, value: slug, }) } if (node.internal.type === "ApiJson" && node.components) { if (node.info.title === "Medusa Storefront API") { createCustomNode({ name: "Store", node: node, createNode: createNode }) } if (node.info.title === "Medusa Admin API") { createCustomNode({ name: "Admin", node: node, createNode: createNode }) } } } const createAllPages = (sections, api, siteData, createPage, template) => { sections.forEach(edge => { const baseURL = convertToKebabCase(edge.section.section_name) createPage({ path: `api/${api}/${baseURL}`, component: template, context: { data: siteData.data[api], api: api, title: edge.section.section_name, description: edge.section.schema ? edge.section.schema.description : "", to: { section: baseURL, method: null, sectionObj: edge.section }, }, }) edge.section.paths.forEach(p => { p.methods.forEach(method => { const methodURL = convertToKebabCase(method.summary) createPage({ path: `api/${api}/${baseURL}/${methodURL}`, component: template, context: { data: siteData.data[api], api: api, title: method.summary, description: method.description || "", to: { section: baseURL, method: methodURL, sectionObj: edge.section }, }, }) }) }) }) } exports.createPages = ({ graphql, actions }) => { const { createPage } = actions const template = path.resolve(`src/templates/reference-page.js`) // Query for markdown nodes to use in creating pages. // You can query for whatever data you want to create pages for e.g. // products, portfolio items, landing pages, etc. // Variables can be added as the second function parameter return graphql( ` query Pages { admin { sections { section { section_name paths { name methods { tags summary description method operationId responses { status description content { _ref type property description json items { type _ref } } } requestBody { type required properties { description enum format property type nestedModel { title properties { property type description } } } } parameters { description in name required schema { type } } } } schema { object description properties { property type description format nestedModel { title properties { property type description } } } } } } } store { sections { section { section_name paths { name methods { tags summary description method operationId responses { status description content { _ref property description json items { _ref } } } requestBody { type required properties { description property type nestedModel { title properties { property type } } } } parameters { description in name required schema { type } } } } schema { object description properties { property type description format nestedModel { title properties { property type } } } } } } } } `, { limit: 1000 } ).then(result => { if (result.errors) { throw result.errors } //create entrypoint createPage({ path: `api`, component: template, context: { data: result.data.store, api: "store", title: "Store", description: "Storefront API", }, }) const apis = [ { title: "Store", description: "Storefront API", slug: "store" }, { title: "Admin", description: "Admin API", slug: "admin" }, ] apis.forEach(api => { //create main page for API createPage({ path: `api/${api.slug}`, component: template, context: { data: result.data[api.slug], api: api.slug, title: api.title, description: api.description, }, }) //create pages for all sections and methods createAllPages( result.data[api.slug].sections, api.slug, result, createPage, template ) }) }) }