diff --git a/gatsby-node.js b/gatsby-node.js index fe15b6aa8fca151e37791ad2771447344cead3c0..dc0b9e016e0f2b1c8e6761f8584e3eed2715febc 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -1,264 +1,302 @@ -const ms = require('ms'); -const chalk = require('chalk'); -const { Directus } = require('@directus/sdk'); -const { sourceNodes, createSchemaCustomization } = require('gatsby-source-graphql/gatsby-node'); -const { createRemoteFileNode } = require('gatsby-source-filesystem'); +const ms = require("ms"); +const chalk = require("chalk"); +const { Directus } = require("@directus/sdk"); +const { + sourceNodes, + createSchemaCustomization, +} = require("gatsby-source-graphql/gatsby-node"); +const { createRemoteFileNode } = require("gatsby-source-filesystem"); /** * Validate plugin options */ exports.pluginOptionsSchema = ({ Joi }) => { - return Joi.object().keys({ - url: Joi.string().required(), - auth: Joi.object() - .keys({ - token: Joi.string(), - email: Joi.string(), - password: Joi.string(), - }) - .with('email', 'password') - .with('password', 'email') - .xor('token', 'email'), - type: Joi.object() - .keys({ - name: Joi.string(), - field: Joi.string(), - }) - .optional(), - dev: Joi.object().keys({ - refresh: [Joi.number(), Joi.string()], - }), - graphql: Joi.object(), - }); + return Joi.object().keys({ + url: Joi.string().required(), + auth: Joi.object() + .keys({ + token: Joi.string(), + email: Joi.string(), + password: Joi.string(), + }) + .with("email", "password") + .with("password", "email") + .xor("token", "email"), + type: Joi.object() + .keys({ + name: Joi.string(), + field: Joi.string(), + }) + .optional(), + dev: Joi.object().keys({ + refresh: [Joi.number(), Joi.string()], + }), + graphql: Joi.object(), + }); }; /** * Gatsby source implementation. */ exports.sourceNodes = async (gatsbyOptions, pluginOptions) => { - await plugin.setOptions(pluginOptions); + await plugin.setOptions(pluginOptions); - const optionsSystem = plugin.getOptionsSystem(); - const options = plugin.getOptions(); + const optionsSystem = plugin.getOptionsSystem(); + const options = plugin.getOptions(); - const createNode = gatsbyOptions.actions.createNode; + const createNode = gatsbyOptions.actions.createNode; - // Avoid type conflict with gatsby-source-graphql - gatsbyOptions.actions.createNode = (node) => { - if (node.internal.type === 'GraphQLSource') { - if (node.typeName === optionsSystem.typeName) node.internal.type = 'DirectusSystemGraphQLSource'; - if (node.typeName === options.typeName) node.internal.type = 'DirectusGraphQLSource'; - } + // Avoid type conflict with gatsby-source-graphql + gatsbyOptions.actions.createNode = (node) => { + if (node.internal.type === "GraphQLSource") { + if (node.typeName === optionsSystem.typeName) + node.internal.type = "DirectusSystemGraphQLSource"; + if (node.typeName === options.typeName) + node.internal.type = "DirectusGraphQLSource"; + } - return createNode(node); - }; + return createNode(node); + }; - await sourceNodes(gatsbyOptions, optionsSystem); - await sourceNodes(gatsbyOptions, options); + await sourceNodes(gatsbyOptions, optionsSystem); + await sourceNodes(gatsbyOptions, options); }; exports.createSchemaCustomization = async (gatsby, pluginOptions) => { - await plugin.setOptions(pluginOptions); + await plugin.setOptions(pluginOptions); - await createSchemaCustomization(gatsby, plugin.getOptionsSystem()); - await createSchemaCustomization(gatsby, plugin.getOptions()); + await createSchemaCustomization(gatsby, plugin.getOptionsSystem()); + await createSchemaCustomization(gatsby, plugin.getOptions()); }; /** * Gatsby file implementation. */ -exports.createResolvers = async ({ actions, cache, createNodeId, createResolvers, store, reporter }, pluginOptions) => { - await plugin.setOptions(pluginOptions); - - const { createNode } = actions; - - const { headers } = await plugin.getOptions(); - const { Authorization } = await headers(); - - const fileResolver = { - imageFile: { - type: `File`, - async resolve(source) { - if (!source || !source.id) return null; - - let filename_download = plugin.fileCache.get(source.id); - - if (!filename_download) { - if (source.filename_download) filename_download = source.filename_download; - else ({ filename_download } = await plugin.directus.files.readOne(source.id)); - - plugin.fileCache.set(source.id, filename_download); - } - - const nameParts = filename_download.split('.'); - const ext = nameParts.length > 1 ? `.${nameParts.pop()}` : ''; - const name = nameParts.join('.'); - - return createRemoteFileNode({ - url: `${plugin.url}assets/${source.id}`, - store, - cache, - createNode, - createNodeId, - httpHeaders: { Authorization }, - reporter, - ext, - name, - }); - }, - }, - }; - - await createResolvers({ - DirectusData_directus_files: fileResolver, - DirectusSystemData_directus_files: fileResolver, - }); +exports.createResolvers = async ( + { actions, cache, createNodeId, createResolvers, store, reporter }, + pluginOptions +) => { + await plugin.setOptions(pluginOptions); + + const { createNode } = actions; + + const { headers } = await plugin.getOptions(); + const { Authorization } = await headers(); + + const fileResolver = { + imageFile: { + type: `File`, + async resolve(source) { + if (!source || !source.id) return null; + + let filename_download = plugin.fileCache.get(source.id); + + if (!filename_download) { + if (source.filename_download) + filename_download = source.filename_download; + else + ({ filename_download } = await plugin.directus.files.readOne( + source.id + )); + + plugin.fileCache.set(source.id, filename_download); + } + + const nameParts = filename_download.split("."); + const ext = nameParts.length > 1 ? `.${nameParts.pop()}` : ""; + const name = nameParts.join("."); + + return createRemoteFileNode({ + url: `${plugin.url}assets/${source.id}`, + store, + cache, + createNode, + createNodeId, + httpHeaders: { Authorization }, + reporter, + ext, + name, + }); + }, + }, + }; + + await createResolvers({ + DirectusData_directus_files: fileResolver, + DirectusSystemData_directus_files: fileResolver, + }); }; class Plugin { - constructor() { - // eslint-disable-next-line no-undef - this.fileCache = new Map(); - this.directus = null; - this.options = null; - this.urlGraphqlSystem = ''; - this.urlGraphql = ''; - this.url = ''; - this.refreshInterval = 0; - this.authPromise = null; - } - - async setOptions(options) { - const { url, dev, auth } = options; - - if (isEmpty(url)) error('"url" must be defined'); - - if (this.directus) return this.authPromise; - - const hasAuth = !!auth; - const hasToken = !isEmpty(auth?.token); - const hasEmail = !isEmpty(auth?.email); - const hasPassword = !isEmpty(auth?.password); - const hasCredentials = hasEmail && hasPassword; - - if (hasAuth) { - if (!hasToken && !hasCredentials) error('"auth.token" or ("auth.email" and "auth.password") must be defined'); - } else warning('no "auth" option were defined. Resources will be fetched with public role'); - - try { - const baseUrl = new URL(url); - const basePath = baseUrl.pathname; - - baseUrl.pathname = basePath; - this.url = baseUrl.toString(); - - baseUrl.pathname = basePath + '/graphql'; - this.urlGraphql = baseUrl.toString(); - - baseUrl.pathname = basePath + '/graphql/system'; - this.urlGraphqlSystem = baseUrl.toString(); - } catch (err) { - error('"url" should be a valid URL'); - } - - try { - this.directus = new Directus(this.url); - - if (hasToken) this.authPromise = await this.directus.auth.static(auth.token); - - if (hasCredentials) - this.authPromise = await this.directus.auth.login({ email: auth?.email, password: auth?.password }); - } catch (err) { - error(`authentication failed with: ${err.message}\nAre credentials valid?`); - } - - this.refreshInterval = typeof dev?.refresh === 'string' ? ms(dev.refresh) / 1000 : dev?.refresh || 15; - - if (Number.isNaN(this.refreshInterval)) - error('"dev.refresh" should be a number in seconds or a string with ms format, i.e. 5s, 5m, 5h, ...'); - - this.options = options; - - return this.authPromise; - } - - getOptions() { - const internalOptions = ['url', 'dev', 'auth', 'type']; - const gatsbyPluginOptions = Object.fromEntries( - Object.entries(this.options).flatMap(([key, value]) => (internalOptions.includes(key) ? [] : [[key, value]])) - ); - - return { - ...this.options.graphql, - ...gatsbyPluginOptions, - url: this.urlGraphql, - typeName: this.options?.type?.name || 'DirectusData', - fieldName: this.options?.type?.field || 'directus', - headers: this.headers.bind(this), - }; - } - - getOptionsSystem() { - const options = this.getOptions(); - - return { - ...options, - url: this.urlGraphqlSystem, - typeName: this.options?.type?.system_name || 'DirectusSystemData', - fieldName: this.options?.type?.system_field || 'directus_system', - }; - } - - async headers() { - let headers = {}; - if (typeof this.options?.headers === 'object') { - Object.assign(headers, this.options.headers || {}); - } else if (typeof this.options?.headers === 'function') { - Object.assign(headers, (await this.options.headers()) || {}); - } - - if (this.directus.auth.token) { - Object.assign(headers, { - Authorization: `Bearer ${this.directus.auth.token}`, - }); - } - - return headers; - } + constructor() { + // eslint-disable-next-line no-undef + this.fileCache = new Map(); + this.directus = null; + this.options = null; + this.urlGraphqlSystem = ""; + this.urlGraphql = ""; + this.url = ""; + this.refreshInterval = 0; + this.authPromise = null; + } + + async setOptions(options) { + const { url, dev, auth } = options; + + if (isEmpty(url)) error('"url" must be defined'); + + if (this.directus) return this.authPromise; + + const hasAuth = !!auth; + const hasToken = !isEmpty(auth?.token); + const hasEmail = !isEmpty(auth?.email); + const hasPassword = !isEmpty(auth?.password); + const hasCredentials = hasEmail && hasPassword; + + if (hasAuth) { + if (!hasToken && !hasCredentials) + error( + '"auth.token" or ("auth.email" and "auth.password") must be defined' + ); + } else + warning( + 'no "auth" option were defined. Resources will be fetched with public role' + ); + + try { + // Forked here to preserve basePath + const baseUrl = new URL(url); + const basePath = baseUrl.pathname; + + baseUrl.pathname = basePath; + this.url = baseUrl.toString(); + + baseUrl.pathname = basePath + "/graphql"; + this.urlGraphql = baseUrl.toString(); + + baseUrl.pathname = basePath + "/graphql/system"; + this.urlGraphqlSystem = baseUrl.toString(); + // + } catch (err) { + error('"url" should be a valid URL'); + } + + try { + this.directus = new Directus(this.url); + + if (hasToken) + this.authPromise = await this.directus.auth.static(auth.token); + + if (hasCredentials) + this.authPromise = await this.directus.auth.login({ + email: auth?.email, + password: auth?.password, + }); + } catch (err) { + error( + `authentication failed with: ${err.message}\nAre credentials valid?` + ); + } + + this.refreshInterval = + typeof dev?.refresh === "string" + ? ms(dev.refresh) / 1000 + : dev?.refresh || 15; + + if (Number.isNaN(this.refreshInterval)) + error( + '"dev.refresh" should be a number in seconds or a string with ms format, i.e. 5s, 5m, 5h, ...' + ); + + this.options = options; + + return this.authPromise; + } + + getOptions() { + const internalOptions = ["url", "dev", "auth", "type"]; + const gatsbyPluginOptions = Object.fromEntries( + Object.entries(this.options).flatMap(([key, value]) => + internalOptions.includes(key) ? [] : [[key, value]] + ) + ); + + return { + ...this.options.graphql, + ...gatsbyPluginOptions, + url: this.urlGraphql, + typeName: this.options?.type?.name || "DirectusData", + fieldName: this.options?.type?.field || "directus", + headers: this.headers.bind(this), + }; + } + + getOptionsSystem() { + const options = this.getOptions(); + + return { + ...options, + url: this.urlGraphqlSystem, + typeName: this.options?.type?.system_name || "DirectusSystemData", + fieldName: this.options?.type?.system_field || "directus_system", + }; + } + + async headers() { + let headers = {}; + if (typeof this.options?.headers === "object") { + Object.assign(headers, this.options.headers || {}); + } else if (typeof this.options?.headers === "function") { + Object.assign(headers, (await this.options.headers()) || {}); + } + + if (this.directus.auth.token) { + Object.assign(headers, { + Authorization: `Bearer ${this.directus.auth.token}`, + }); + } + + return headers; + } } class Log { - static log(level, message) { - let color = level === 'error' ? 'red' : level === 'warning' ? 'yellow' : 'white'; - - // eslint-disable-next-line no-console - console.log(chalk.cyan('gatsby-source-directus'), ':', chalk[color](message)); - } - static error(message) { - Log.log('error', message); - } - static warning(message) { - Log.log('error', message); - } + static log(level, message) { + let color = + level === "error" ? "red" : level === "warning" ? "yellow" : "white"; + + // eslint-disable-next-line no-console + console.log( + chalk.cyan("gatsby-source-directus"), + ":", + chalk[color](message) + ); + } + static error(message) { + Log.log("error", message); + } + static warning(message) { + Log.log("error", message); + } } function isEmpty(value) { - if (value?.constructor === String) return value.length === 0; + if (value?.constructor === String) return value.length === 0; - return true; + return true; } function error(message) { - Log.error(message); + Log.error(message); - const error = new Error(`gatsby-source-directus: ${message}`); - error.stack = undefined; + const error = new Error(`gatsby-source-directus: ${message}`); + error.stack = undefined; - throw error; + throw error; } function warning(message) { - Log.warning(message); + Log.warning(message); } const plugin = new Plugin(); diff --git a/package.json b/package.json index 6bb6422045e45960c37f7a721c2627aa957ec37b..578530a0f9973f040e5221c206f2de6e4a912c03 100644 --- a/package.json +++ b/package.json @@ -1,23 +1,23 @@ { - "name": "@directus/gatsby-source-directus", - "version": "9.14.1", - "description": "Source plugin for pulling data into Gatsby from a Directus API.", - "author": "João Biondo <wolfulus@gmail.com>", - "license": "MIT", - "keywords": [ - "gatsby", - "gatsby-plugin", - "directus" - ], - "dependencies": { - "@directus/sdk": "9.14.1", - "gatsby-source-filesystem": "4.13.0", - "gatsby-source-graphql": "4.13.0", - "ms": "2.1.3" - }, - "repository": "directus/gatsby-source-directus", - "bugs": { - "url": "https://github.com/directus/directus/issues" - }, - "gitHead": "24621f3934dc77eb23441331040ed13c676ceffd" + "name": "@onegeo/gatsby-source-directus", + "version": "9.14.1", + "description": "Source plugin for pulling data into Gatsby from a Directus API.", + "author": "João Biondo <wolfulus@gmail.com>", + "license": "MIT", + "keywords": [ + "gatsby", + "gatsby-plugin", + "directus" + ], + "dependencies": { + "@directus/sdk": "9.14.1", + "gatsby-source-filesystem": "4.13.0", + "gatsby-source-graphql": "4.13.0", + "ms": "2.1.3" + }, + "repository": "onegeo/gatsby-source-directus", + "bugs": { + "url": "https://github.com/directus/directus/issues" + }, + "gitHead": "24621f3934dc77eb23441331040ed13c676ceffd" } diff --git a/readme.md b/readme.md index 960717d93a2e8589f171cf33b40e682db535b42a..fc2e8e0b049447bf39203869695c6d6caf3f4e62 100644 --- a/readme.md +++ b/readme.md @@ -2,10 +2,12 @@ Source plugin for pulling data into Gatsby from a Directus API. +Forked from https://github.com/directus/gatsby-source-directus + ## Install ``` -npm install --save @directus/gatsby-source-directus +npm install --save @onegeo/gatsby-source-directus ``` ## Usage @@ -15,31 +17,31 @@ npm install --save @directus/gatsby-source-directus ```js module.exports = { - // ... some gatsby configuration - plugins: [ - // ... some gatsby plugins - - // You can take advantage of the following plugins with gatsby-source-directus - - // `gatsby-plugin-image`, - // `gatsby-transformer-sharp`, - // `gatsby-plugin-sharp`, - - // Finally our plugin - { - resolve: '@directus/gatsby-source-directus', - options: { - url: `https://myproject.directus.cloud`, // Fill with your Directus instance address - auth: { - token: 'my_secret_token', // You can use a static token from an user - - // Or you can use the credentials of an user - // email: "johndoe@directus.cloud", - // password: "mysecretpassword", - }, - }, - }, - ], + // ... some gatsby configuration + plugins: [ + // ... some gatsby plugins + + // You can take advantage of the following plugins with gatsby-source-directus + + // `gatsby-plugin-image`, + // `gatsby-transformer-sharp`, + // `gatsby-plugin-sharp`, + + // Finally our plugin + { + resolve: "@onegeo/gatsby-source-directus", + options: { + url: `https://myproject.directus.cloud/directus`, // Fill with your Directus instance address + auth: { + token: "my_secret_token", // You can use a static token from an user + + // Or you can use the credentials of an user + // email: "johndoe@directus.cloud", + // password: "mysecretpassword", + }, + }, + }, + ], }; ``` @@ -47,42 +49,42 @@ module.exports = { ```graphql query { - # if you change `type.name`, remember to also rename following field - directus { - # the collection you want to query - articles { - # the fields you want to query from above collection - title - files { - # since this is a M2M relationship, we need to reference the junction field - directus_files_id { - # `id` is required to be fetched in order to be used with `gatsby-transformer-sharp` - id - imageFile { - # when using the plugin 'gatsby-transformer-sharp', you can query images with transformations - childImageSharp { - gatsbyImageData(width: 200) - } - } - } - } - } - } - - # it's also possible to query system collections - directus_system { - users { - email - } - files { - id - imageFile { - childImageSharp { - gatsbyImageData(width: 200) - } - } - } - } + # if you change `type.name`, remember to also rename following field + directus { + # the collection you want to query + articles { + # the fields you want to query from above collection + title + files { + # since this is a M2M relationship, we need to reference the junction field + directus_files_id { + # `id` is required to be fetched in order to be used with `gatsby-transformer-sharp` + id + imageFile { + # when using the plugin 'gatsby-transformer-sharp', you can query images with transformations + childImageSharp { + gatsbyImageData(width: 200) + } + } + } + } + } + } + + # it's also possible to query system collections + directus_system { + users { + email + } + files { + id + imageFile { + childImageSharp { + gatsbyImageData(width: 200) + } + } + } + } } ``` @@ -135,14 +137,14 @@ The default way to query data is to fetch items from `directus` field. ```graphql query { - directus { - items { - my_collection { - some_field - other_field - } - } - } + directus { + items { + my_collection { + some_field + other_field + } + } + } } ``` @@ -150,28 +152,28 @@ If you specify the `type.field`, you must query from that field instead. ```graphql query { - # In this case `type.field` is "blog" - blog { - items { - posts { - id - title - slug - status - } - } - } - # While in this case `type.field` is "portal" - portal { - items { - pages { - id - title - slug - status - } - } - } + # In this case `type.field` is "blog" + blog { + items { + posts { + id + title + slug + status + } + } + } + # While in this case `type.field` is "portal" + portal { + items { + pages { + id + title + slug + status + } + } + } } ```