Skip to content

Commit

Permalink
support strapi 5
Browse files Browse the repository at this point in the history
  • Loading branch information
laurenskling committed Sep 28, 2024
1 parent 47d4db8 commit 31fa710
Show file tree
Hide file tree
Showing 9 changed files with 131 additions and 125 deletions.
13 changes: 13 additions & 0 deletions .changeset/thick-eggs-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"gatsby-source-strapi": major
---

Support Strapi v5, while staying compatible with v4.

There are no breaking changes. I've created a major release because I don't want to bother current applications with possible bugs. Updrading to this code should be a choice.

I have removed the code for creating the unstable_createNodeManifest. In my believe, this was only for Gatsby Cloud and as Gatsby Cloud only remains in our sweet memories of the glory days, we don't need it anymore. I've also deleted the readme about Gatsby Cloud and content sync, I don't believe any plaform is supporting content sync right now, or ever again.

I have tried to support both v4 and v5 syntaxes in this release. By setting `version` on your config to 5, the new REST API syntax will be used. `publicationState=preview` will automatically be rewritten to `status=draft`.

As Strapi v5 is using documentId's over regular id's, I am now using the documentId (where available, f.e. not in components) to create the Gatsby Node id. This should keep updated relations intact when reloading data.
13 changes: 13 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const globals = require("globals");
const ts_parser = require("@typescript-eslint/parser");
const babel_parser = require("@babel/eslint-parser");
const unicorn = require("eslint-plugin-unicorn");
Expand Down Expand Up @@ -77,4 +78,16 @@ module.exports = [
...typescript.configs.recommended.rules,
},
},
{
files: ["packages/gatsby-source-strapi/**/*.js"],
rules: {
"no-undef": "error",
"no-unused-vars": "error",
},
languageOptions: {
globals: {
...globals.node,
},
},
},
];
57 changes: 5 additions & 52 deletions packages/gatsby-source-strapi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Source plugin for pulling documents into Gatsby from a Strapi API.

> ⚠️ This version of `gatsby-source-strapi` is only compatible with Strapi v4. For v3 use this [release](https://www.npmjs.com/package/gatsby-source-strapi/v/1.0.3)
> ⚠️ This version of `gatsby-source-strapi` is only compatible with Strapi v5 and v4. For v3 use this [release](https://www.npmjs.com/package/gatsby-source-strapi/v/1.0.3)
_This plugin is now maintained publicly by the Gatsby User Collective. Join us in maintaining this piece of software._

Expand Down Expand Up @@ -73,6 +73,7 @@ require("dotenv").config({
});

const strapiConfig = {
version: 4, // Strapi version 4 or 5
apiURL: process.env.STRAPI_API_URL,
accessToken: process.env.STRAPI_TOKEN,
collectionTypes: ["article", "company", "author"],
Expand Down Expand Up @@ -151,6 +152,9 @@ const strapiConfig = {
singularName: "article",
queryParams: {
publicationState: process.env.GATSBY_IS_PREVIEW === "true" ? "preview" : "live",
// or the v5 format,
// we will automatically rewrite publicationState to status:
// status: process.env.GATSBY_IS_PREVIEW === "true" ? "draft" : "published",
populate: {
category: { populate: "*" },
cover: "*",
Expand Down Expand Up @@ -370,57 +374,6 @@ Then use the one of the following queries to fetch a localized content type:
}
```

## Gatsby cloud and preview environment setup

### Setup

To enable content sync in [Gatsby cloud](https://www.gatsbyjs.com/docs/how-to/previews-deploys-hosting/deploying-to-gatsby-cloud/) you need to create two webhooks in your Strapi project:

- [Build webhook](https://support.gatsbyjs.com/hc/en-us/articles/360052324394-Build-and-Preview-Webhooks)

![webhook setup](./assets/webhook.png)

- [Preview webhook](https://support.gatsbyjs.com/hc/en-us/articles/360052324394-Build-and-Preview-Webhooks)

At this point each time you create an entry the webhooks will trigger a new build a deploy your new Gatsby site.

In the Site settings, Environment variables fill the:

- Build variables with the following:
- STRAPI_API_URL with the url of your deployed Strapi application
- STRAPI_TOKEN with your build API token
- Preview variables:
- STRAPI_API_URL with the url of your deployed Strapi application
- STRAPI_TOKEN with your preview API token

### Enabling Content Sync

#### Installing the @strapi/plugin-gatsby-preview

In order to enable Gatsby Content Sync and integrate it in your Strapi CMS you need to install the `@strapi/plugin-gatsby-preview` in your Strapi project:

##### Using yarn

```sh
cd my-strapi-app

yarn add @strapi/plugin-gatsby-preview
```

##### Using npm

```sh
cd my-strapi-app
npm install --save @strapi/plugin-gatsby-preview
```

#### Configurations

Once the plugin is installed, you will need to configure it in the plugin's settings section.

- In the Collection types or the Single Types tab, when enabling the **preview**, it will inject a button in the content manager edit view of the corresponding content type. So, after creating an entry (draft or published), clicking on the **Open Gatsby preview** button will redirect you to the Gatsby preview page
- In the Settings tab, enter the Gatsby Content Sync URL. You can find it in Gatsby cloud under "Site settings" and "Content Sync".

## Restrictions and limitations

This plugin has several limitations, please be aware of these:
Expand Down
2 changes: 1 addition & 1 deletion packages/gatsby-source-strapi/src/axios-instance.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const throttlingInterceptors = (axiosInstance, maxParallelRequests) => {

/** Axios Request Interceptor */
axiosInstance.interceptors.request.use(function (config) {
return new Promise((resolve, _) => {
return new Promise((resolve) => {
let interval = setInterval(() => {
if (PENDING_REQUESTS < maxParallelRequests) {
PENDING_REQUESTS++;
Expand Down
70 changes: 48 additions & 22 deletions packages/gatsby-source-strapi/src/clean-data.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import _ from "lodash";
import { getContentTypeSchema } from "./helpers";

const MEDIA_FIELDS = [
"id",
"name",
"alternativeText",
"caption",
Expand All @@ -17,18 +18,45 @@ const MEDIA_FIELDS = [
"previewUrl",
"createdAt",
"updatedAt",
"documentId",
"publishedAt",
];

const restrictedFields = new Set(["__component", `children`, `fields`, `internal`, `parent`]);

const getValue = (value, version) => {
if (!value) {
return;
}
if (version === 4) {
return value?.data;
}
// assume v5
return value;
};

const getAttributes = (data, version) => {
if (!data) {
return;
}
if (version === 4) {
return {
id: data.id,
...data.attributes,
};
}
// assume v5
return data;
};

/**
* Removes the attribute key in the entire data.
* @param {Object} attributes response from the API
* @param {Object} currentSchema
* @param {*} schemas
* @returns
*/
export const cleanAttributes = (attributes, currentSchema, schemas) => {
export const cleanAttributes = (attributes, currentSchema, schemas, version = 4) => {
if (!attributes) {
return;
}
Expand Down Expand Up @@ -69,7 +97,7 @@ export const cleanAttributes = (attributes, currentSchema, schemas) => {
[attributeName]: value.map((v) => {
const compoSchema = getContentTypeSchema(schemas, v.__component);

return cleanAttributes(v, compoSchema, schemas);
return cleanAttributes(v, compoSchema, schemas, version);
}),
};
}
Expand All @@ -82,59 +110,60 @@ export const cleanAttributes = (attributes, currentSchema, schemas) => {
return {
...accumulator,
[attributeName]: value.map((v) => {
return cleanAttributes(v, compoSchema, schemas);
return cleanAttributes(v, compoSchema, schemas, version);
}),
};
}

return {
...accumulator,
[attributeName]: cleanAttributes(value, compoSchema, schemas),
[attributeName]: cleanAttributes(value, compoSchema, schemas, version),
};
}

// make sure we can use both v4 and v5 outputs
const valueData = getValue(value, version);

if (attribute.type === "media") {
if (Array.isArray(value?.data)) {
if (Array.isArray(valueData)) {
return {
...accumulator,
[attributeName]: value.data
? value.data.map(({ id, attributes }) => ({
id,
..._.pick(attributes, MEDIA_FIELDS),
[attributeName]: valueData
? valueData.map((data) => ({
..._.pick(getAttributes(data, version), MEDIA_FIELDS),
}))
: undefined,
};
}

return {
...accumulator,
[attributeName]: value.data
[attributeName]: valueData
? {
id: value.data.id,
..._.pick(value.data.attributes, MEDIA_FIELDS),
..._.pick(getAttributes(valueData, version), MEDIA_FIELDS),
}
: undefined,
};
}

if (attribute.type === "relation") {
const relationSchema = getContentTypeSchema(schemas, attribute.target);

if (Array.isArray(value?.data)) {
if (Array.isArray(valueData)) {
return {
...accumulator,
[attributeName]: value.data.map(({ id, attributes }) =>
cleanAttributes({ id, ...attributes }, relationSchema, schemas),
[attributeName]: valueData.map((data) =>
cleanAttributes(getAttributes(data, version), relationSchema, schemas, version),
),
};
}

return {
...accumulator,
[attributeName]: cleanAttributes(
value.data ? { id: value.data.id, ...value.data.attributes } : undefined,
getAttributes(valueData, version) || undefined,
relationSchema,
schemas,
version,
),
};
}
Expand All @@ -152,13 +181,10 @@ export const cleanAttributes = (attributes, currentSchema, schemas) => {
* @param {Object} ctx
* @returns {Object}
*/
export const cleanData = ({ id, attributes, ...rest }, context) => {
export const cleanData = (data, context, version = 4) => {
const { schemas, contentTypeUid } = context;
const currentContentTypeSchema = getContentTypeSchema(schemas, contentTypeUid);

return {
id,
...rest,
...cleanAttributes(attributes, currentContentTypeSchema, schemas),
...cleanAttributes(getAttributes(data, version), currentContentTypeSchema, schemas, version),
};
};
35 changes: 29 additions & 6 deletions packages/gatsby-source-strapi/src/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,34 @@ export const fetchStrapiContentTypes = async (axiosInstance) => {
};
};

export const fetchEntity = async ({ endpoint, queryParams, uid, pluginOptions }, context) => {
const convertQueryParameters = (queryParameters, version = 4) => {
if (version == 4) {
return queryParameters;
}
// assume v5.
// rewrite v4 publicationState=preview to status=draft
// https://docs.strapi.io/dev-docs/migration/v4-to-v5/breaking-changes/publication-state-removed
const { publicationState, ...rest } = queryParameters;
if (publicationState !== "preview") {
return queryParameters;
}
return {
...rest,
status: "draft",
};
};

export const fetchEntity = async (
{ endpoint, queryParams, uid, pluginOptions, version = 4 },
context,
) => {
const { reporter, axiosInstance } = context;

/** @type AxiosRequestConfig */
const options = {
method: "GET",
url: endpoint,
params: queryParams,
params: convertQueryParameters(queryParams, version),
// Source: https://github.com/axios/axios/issues/5058#issuecomment-1379970592
paramsSerializer: {
serialize: (parameters) => qs.stringify(parameters, { encodeValuesOnly: true }),
Expand Down Expand Up @@ -91,7 +111,7 @@ export const fetchEntity = async ({ endpoint, queryParams, uid, pluginOptions },
const otherLocalizationsData = await Promise.all(otherLocalizationsPromises);

return castArray([data.data, ...otherLocalizationsData]).map((entry) =>
cleanData(entry, { ...context, contentTypeUid: uid }),
cleanData(entry, { ...context, contentTypeUid: uid }, version),
);
} catch (error) {
if (error.response.status !== 404) {
Expand All @@ -104,14 +124,17 @@ export const fetchEntity = async ({ endpoint, queryParams, uid, pluginOptions },
}
};

export const fetchEntities = async ({ endpoint, queryParams, uid, pluginOptions }, context) => {
export const fetchEntities = async (
{ endpoint, queryParams, uid, pluginOptions, version = 4 },
context,
) => {
const { reporter, axiosInstance } = context;

/** @type AxiosRequestConfig */
const options = {
method: "GET",
url: endpoint,
params: queryParams,
params: convertQueryParameters(queryParams, version),
paramsSerializer: {
serialize: (parameters) => qs.stringify(parameters, { encodeValuesOnly: true }),
},
Expand Down Expand Up @@ -176,7 +199,7 @@ export const fetchEntities = async ({ endpoint, queryParams, uid, pluginOptions
const results = await Promise.all(fetchPagesPromises);

const cleanedData = [...data, ...flattenDeep(results)].map((entry) =>
cleanData(entry, { ...context, contentTypeUid: uid }),
cleanData(entry, { ...context, contentTypeUid: uid }, version),
);

return cleanedData;
Expand Down
Loading

0 comments on commit 31fa710

Please sign in to comment.