docs: editing and general fixes of medusa's learning resources (#7261)

* docs: editing and general fixes of medusa's learning resources

* fix build script

* update ui dependency

* fix build

* adjust next.js steps
This commit is contained in:
Shahed Nasser
2024-05-13 18:55:11 +03:00
committed by GitHub
parent 803e4aad02
commit 7cb90f8e82
79 changed files with 488 additions and 1707 deletions
@@ -0,0 +1,686 @@
import { Table } from "docs-ui"
export const metadata = {
title: `Contentful Plugin`,
}
# {metadata.title}
## Features
[Contentful](https://www.contentful.com/) is a headless CMS service that allows developers to integrate rich CMS functionalities into any platform.
By integrating Contentful to Medusa, you can benefit from powerful features in your ecommerce store such as:
- Rich CMS details for product.
- Easy-to-use interface to manage content for static content and pages.
- Localization for product and storefront content.
- Two-way sync between Contentful and Medusa.
---
## Install the Contentful Plugin
<Note type="check">
- [Contentful account with a space](https://www.contentful.com/sign-up/).
- An Event Module installed in the Medusa application, such as the [Redis Event Module](../../../architectural-modules/event/redis/page.mdx).
</Note>
To install the Contentful plugin, run the following command in the directory of your Medusa application:
```bash npm2yarn
npm install medusa-plugin-contentful
```
Next, add the plugin into the `plugins` array in `medusa-config.js`:
export const highlights = [
["6", "space_id", "The Contentful space's ID."],
["7", "access_token", "The personal access token for content management."],
["8", "environment", "The Contentful environment."],
]
```js title="medusa-config.js" highlights={highlights}
const plugins = [
// ...
{
resolve: `medusa-plugin-contentful`,
options: {
space_id: process.env.CONTENTFUL_SPACE_ID,
access_token: process.env.CONTENTFUL_ACCESS_TOKEN,
environment: process.env.CONTENTFUL_ENV,
},
},
]
```
### Contentful Plugin Options
<Table>
<Table.Header>
<Table.Row>
<Table.HeaderCell>Option</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
<Table.HeaderCell>Required</Table.HeaderCell>
<Table.HeaderCell>Default</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
<Table.Row>
<Table.Cell>
`space_id`
</Table.Cell>
<Table.Cell>
A string indicating the ID of your [Contentful space](https://www.contentful.com/help/find-space-id/).
</Table.Cell>
<Table.Cell>
Yes
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`access_token`
</Table.Cell>
<Table.Cell>
A string indicating [the personal access token for content management](https://www.contentful.com/help/personal-access-tokens/#how-to-get-a-personal-access-token-the-web-app).
</Table.Cell>
<Table.Cell>
Yes
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`environment`
</Table.Cell>
<Table.Cell>
A string indicating the [Contentful environment](https://www.contentful.com/developers/docs/concepts/multiple-environments/). Typically, its value should be `master`.
</Table.Cell>
<Table.Cell>
Yes
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`ignore_threshold`
</Table.Cell>
<Table.Cell>
The number of seconds to wait before re-syncing a specific record.
</Table.Cell>
<Table.Cell>
No
</Table.Cell>
<Table.Cell>
`2`
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
`custom_<TYPE>_fields`
</Table.Cell>
<Table.Cell>
An object that allows you to map fields in Medusa to [custom field names](#custom-field-mapping).
</Table.Cell>
<Table.Cell>
No
</Table.Cell>
<Table.Cell>
\-
</Table.Cell>
</Table.Row>
</Table.Body>
</Table>
### Environment Variables
Make sure to add the following environment variables.
```bash
CONTENTFUL_SPACE_ID=<YOUR_SPACE_ID>
CONTENTFUL_ACCESS_TOKEN=<YOUR_ACCESS_TOKEN>
CONTENTFUL_ENV=master
```
### Custom Field Mapping
When the plugin syncs data between Contentful and Medusa, it expects a set of fields to be defined in the respective content models in Contentful. If you use different names to define those fields in Contentful, specify them in the `custom_<TYPE>_fields` option mentioned earlier, where `<TYPE>` is the name of the content model.
For example, to change the name of the products `title` field, pass the following option to the plugin:
```js title="medusa-config.js" highlights={[["9"], ["10"], ["11"]]}
const plugins = [
// ...
{
resolve: `medusa-plugin-contentful`,
options: {
space_id: process.env.CONTENTFUL_SPACE_ID,
access_token: process.env.CONTENTFUL_ACCESS_TOKEN,
environment: process.env.CONTENTFUL_ENV,
custom_product_fields: {
title: "name",
},
},
},
]
```
The rest of this section includes the field names you can customize using this option for each content model type.
<Details summaryContent="product">
- `title`
- `subtitle`
- `description`
- `variants`
- `options`
- `medusaId`
- `type`
- `collection`
- `tags`
- `handle`
</Details>
<Details summaryContent="variant">
- `title`
- `sku`
- `prices`
- `options`
- `medusaId`
</Details>
<Details summaryContent="region">
- `name`
- `countries`
- `paymentProviders`
- `fulfillmentProviders`
- `medusaId`
</Details>
<Details summaryContent="collection">
- `title`
- `medusaId`
</Details>
<Details summaryContent="type">
- `name`
- `medusaId`
</Details>
### Migrate Content Models
In your Contentful space, you must have content models for Medusa entities such as products and regions.
You can either create the content models manually, or create a loader in the Medusa application that migrates these content models into Contentful.
This section includes migration scripts for Medusas data models that are relevant for Contentful.
Before creating the migration scripts, run the following command in the root of your Medusa backend to install Contentfuls migration SDK:
```bash npm2yarn
npm install --save-dev contentful-migration
```
<Details summaryContent="product Content Model">
Create the file `src/loaders/contentful-migrations/product.ts` with the following content:
```ts title="src/loaders/contentful-migrations/product.ts"
import Migration, {
MigrationContext,
} from "contentful-migration"
export function productMigration(
migration: Migration,
context?: MigrationContext
) {
const product = migration
.createContentType("product")
.name("Product")
.displayField("title")
product
.createField("title")
.name("Title")
.type("Symbol")
.required(true)
product
.createField("subtitle")
.name("Subtitle")
.type("Symbol")
product
.createField("handle")
.name("Handle")
.type("Symbol")
product
.createField("thumbnail")
.name("Thumbnail")
.type("Link")
.linkType("Asset")
product
.createField("description")
.name("Description")
.type("Text")
product
.createField("options")
.name("Options")
.type("Object")
product
.createField("tags")
.name("Tags")
.type("Object")
product
.createField("collection")
.name("Collection")
.type("Symbol")
product
.createField("type")
.name("Type")
.type("Symbol")
product
.createField("variants")
.name("Variants")
.type("Array")
.items({
type: "Link",
linkType: "Entry",
validations: [
{
linkContentType: ["productVariant"],
},
],
})
product
.createField("medusaId")
.name("Medusa ID")
.type("Symbol")
}
```
</Details>
<Details summaryContent="productVariant Content Model">
Create the file `src/loaders/contentful-migrations/product-variant.ts` with the following content:
```ts title="src/loaders/contentful-migrations/product-variant.ts"
import Migration, {
MigrationContext,
} from "contentful-migration"
export function productVariantMigration(
migration: Migration,
context?: MigrationContext
) {
const productVariant = migration
.createContentType("productVariant")
.name("Product Variant")
.displayField("title")
productVariant
.createField("title")
.name("Title")
.type("Symbol")
.required(true)
productVariant
.createField("sku")
.name("SKU")
.type("Symbol")
productVariant
.createField("options")
.name("Options")
.type("Object")
productVariant
.createField("prices")
.name("Prices")
.type("Object")
productVariant
.createField("medusaId")
.name("Medusa ID")
.type("Symbol")
}
```
</Details>
<Details summaryContent="collection Content Model">
Create the file `src/loaders/contentful-migrations/product-collection.ts` with the following content:
```ts title="src/loaders/contentful-migrations/product-collection.ts"
import Migration, {
MigrationContext,
} from "contentful-migration"
export function productCollectionMigration(
migration: Migration,
context?: MigrationContext
) {
const collection = migration
.createContentType("collection")
.name("Product Collection")
.displayField("title")
collection
.createField("title")
.name("Title")
.type("Symbol")
.required(true)
collection
.createField("medusaId")
.name("Medusa ID")
.type("Symbol")
}
```
</Details>
<Details summaryContent="productType Content Model">
Create the file `src/loaders/contentful-migrations/product-type.ts` with the following content:
```ts title="src/loaders/contentful-migrations/product-type.ts"
import Migration, {
MigrationContext,
} from "contentful-migration"
export function productTypeMigration(
migration: Migration,
context?: MigrationContext
) {
const collection = migration
.createContentType("productType")
.name("Product Type")
.displayField("title")
collection
.createField("title")
.name("Title")
.type("Symbol")
.required(true)
collection
.createField("medusaId")
.name("Medusa ID")
.type("Symbol")
}
```
</Details>
<Details summaryContent="region Content Model">
Create the file `src/loaders/contentful-migrations/region.ts` with the following content:
```ts title="src/loaders/contentful-migrations/region.ts"
import Migration, {
MigrationContext,
} from "contentful-migration"
export function regionMigration(
migration: Migration,
context?: MigrationContext
) {
const region = migration
.createContentType("region")
.name("Region")
.displayField("name")
region
.createField("name")
.name("Name")
.type("Symbol")
.required(true)
region
.createField("countries")
.name("Options")
.type("Object")
region
.createField("paymentProviders")
.name("Payment Providers")
.type("Object")
region
.createField("fulfillmentProviders")
.name("Fulfillment Providers")
.type("Object")
region
.createField("currencyCode")
.name("Currency Code")
.type("Symbol")
region
.createField("medusaId")
.name("Medusa ID")
.type("Symbol")
}
```
</Details>
Finally, create a loader at `src/loaders/index.ts` with the following content:
```ts title="src/loaders/index.ts"
import {
ConfigModule,
StoreService,
MedusaContainer,
} from "@medusajs/medusa"
import { runMigration } from "contentful-migration"
import {
productMigration,
} from "./contentful-migrations/product"
import {
productVariantMigration,
} from "./contentful-migrations/product-variant"
import {
productCollectionMigration,
} from "./contentful-migrations/product-collection"
import {
productTypeMigration,
} from "./contentful-migrations/product-type"
import {
regionMigration,
} from "./contentful-migrations/region"
type ContentfulPluginType = {
resolve: string
options: {
space_id: string
access_token: string
environment: string
}
}
export default async (
container: MedusaContainer,
config: ConfigModule
): Promise<void> => {
// ensure that migration only runs once
const storeService = container.resolve<StoreService>(
"storeService"
)
const store = await storeService.retrieve()
if (store.metadata?.ran_contentful_migrations) {
return
}
console.info("Running contentful migrations...")
// load Contentful options
const contentfulPlugin = config.plugins
.find((plugin) =>
typeof plugin === "object" &&
plugin.resolve === "medusa-plugin-contentful"
) as ContentfulPluginType
if (!contentfulPlugin) {
console.log(
"Didn't find Contentful plugin. Aborting migration..."
)
return
}
const options = {
spaceId: contentfulPlugin.options.space_id,
accessToken: contentfulPlugin.options.access_token,
environment: contentfulPlugin.options.environment,
yes: true,
}
const migrationFunctions = [
{
name: "Product",
function: productMigration,
},
{
name: "Product Variant",
function: productVariantMigration,
},
{
name: "Product Collection",
function: productCollectionMigration,
},
{
name: "Product Type",
function: productTypeMigration,
},
{
name: "Region",
function: regionMigration,
},
]
await Promise.all(
migrationFunctions.map(async (migrationFunction) => {
console.info(`Migrating ${
migrationFunction.name
} component...`)
try {
await runMigration({
...options,
migrationFunction: migrationFunction.function,
})
console.info(`Finished migrating ${
migrationFunction.name
} component`)
} catch (e) {
if (
typeof e === "object" && "errors" in e &&
Array.isArray(e.errors) &&
e.errors.length > 0 &&
e.errors[0].type === "Invalid Action" &&
e.errors[0].message.includes("already exists")
) {
console.info(`${
migrationFunction.name
} already exists. Skipping its migration.`)
} else {
throw new Error(e)
}
}
})
)
await storeService.update({
metadata: {
ran_contentful_migrations: true,
},
})
console.info("Finished contentful migrations")
}
```
Notice that in the script you store a flag in the default stores `metadata` attribute to ensure these migrations only run once.
### Setup Webhooks
As mentioned in the introduction, this plugin supports two-way sync. A subscriber in the plugin listens to changes in the data, such as adding a new product, and syncs the data with Contentful.
To update the Medusa application when changes occur in Contentful, you must configure webhooks settings in Contentful.
<Note>
For webhooks to work, your Medusa application must be deployed and accessible publicly.
</Note>
To do that:
1. On your Contentful Space Dashboard, click on Settings from the navigation bar, then choose Webhooks.
2. Click on the Add Webhook button.
3. In the form, enter a name for the webhook.
4. In the URL field, choose the method `POST` and in the input next to it enter the URL `<MEDUSA_URL>/hooks/contentful` where `<MEDUSA_URL>` is the URL of your deployed Medusa application.
5. Scroll down to find the Content Type select field. Choose `application/json` as its value.
6. You can leave the rest of the fields the same and click on the Save button.
---
## Test the Plugin
Run the following command to start your Medusa application and test the plugin:
```bash npm2yarn
npm run dev
```
If you created migration scripts, theyll run when the Medusa application starts and migrate your content models to Contentful. You can go to your spaces dashboard to confirm theyve been created.
After that, try the sync functionality by creating or updating products in the Medusa application. If youve also setup webhooks, you can test out the sync from Contentful to Medusa.
@@ -0,0 +1,229 @@
export const metadata = {
title: `Strapi Plugin`,
}
# {metadata.title}
<Note>
This plugin is a [community plugin](https://github.com/SGFGOV/medusa-strapi-repo) and is not managed by the official Medusa team. It supports v4 of Strapi. If you run into any issues, please refer to the [plugin's repository](https://github.com/SGFGOV/medusa-strapi-repo).
</Note>
## Features
[Strapi](https://strapi.io/) is an open source headless CMS service that allows developers to have complete control over their content models. It can be integrated into many other frameworks, including Medusa.
By integrating Strapi into Medusa, you can benefit from powerful features in your ecommerce store, such as:
- Rich CMS details for product.
- Easy-to-use interface to manage content for static content and pages.
- Localization for product and storefront content.
- Two-way sync between Strapi and Medusa.
---
## Preparations
<Note type="check">
- A [PostgreSQL database](https://www.postgresql.org/docs/current/sql-createdatabase.html) for Strapi.
</Note>
In this section, youll setup a Strapi project with a Medusa plugin installed. To do that:
1. Clone the Strapi project repository:
```bash
git clone https://github.com/SGFGOV/medusa-strapi-repo.git
```
2. Change to the `medusa-strapi-repo/packages/medusa-strapi` directory.
3. Copy the `.env.test` file to a new `.env` file.
### Change Strapi Environment Variables
In the `.env` file, change the following environment variables:
```bash
# IMPORTANT: Change supersecret with random and unique strings
APP_KEYS=supersecret
API_TOKEN_SALT=supersecret
ADMIN_JWT_SECRET=supersecret
JWT_SECRET=supersecret
MEDUSA_STRAPI_SECRET=supersecret
MEDUSA_BACKEND_URL=http://localhost:9000
MEDUSA_BACKEND_ADMIN=http://localhost:7001
SUPERUSER_EMAIL=support@medusa-commerce.com
SUPERUSER_USERNAME=SuperUser
SUPERUSER_PASSWORD=MedusaStrapi1
DATABASE_HOST=localhost
DATABASE_PORT=5432
DATABASE_NAME=postgres_strapi
DATABASE_USERNAME=postgres
DATABASE_PASSWORD=
DATABASE_SSL=false
DATABASE_SCHEMA=public
```
1. Change `APP_KEYS`, `API_TOKEN_SALT`, `JWT_SECRET`, and `ADMIN_JWT_SECRET` to a random and unique string. These keys are used by Strapi to sign session cookies, generate API tokens, and more.
2. Change `MEDUSA_STRAPI_SECRET` to a random unique string. The value of this environment variable is used later in your Medusa configurations.
3. Change `MEDUSA_BACKEND_URL` to the URL of your Medusa backend. If youre running it locally, it should be `http://localhost:9000`.
4. Change `MEDUSA_BACKEND_ADMIN` to the URL of your Medusa Admin. If youre running it locally, it should be `http://localhost:7001`.
5. Change the following environment variables to define the Strapi super user:
1. `SUPERUSER_EMAIL`: the super users email. By default, its `support@medusa-commerce.com`.
2. `SUPERUSER_USERNAME`: the super users username. By default, its `SuperUser`.
3. `SUPERUSER_PASSWORD`: the super users password. By default, its `MedusaStrapi1`.
4. `SUPERUSER_FIRSTNAME`: the super users first name. By default, its `Medusa`.
5. `SUPERUSER_LASTNAME`: the super users last name. By default, its `Commerce`.
6. Change the database environment variables based on your database configurations. All database environment variables start with `DATABASE_`.
7. You can optionally configure other services, such as S3 or MeiliSearch, as explained [here](https://github.com/SGFGOV/medusa-strapi-repo/tree/development/packages/medusa-strapi#media-bucket).
### Build Packages
Once youre done, install and build packages in the root `medusa-strapi-repo` directory:
```bash npm2yarn
# Install packages
npm install
# Build packages
npm run build
```
---
## Install the Strapi Plugin in Medusa
<Note type="check">
- An Event Module installed in the Medusa application, such as the [Redis Event Module](../../../architectural-modules/event/redis/page.mdx).
</Note>
To install the Strapi plugin, run the following command in the directory of your Medusa application:
```bash npm2yarn
npm install medusa-plugin-strapi-ts
```
Next, add the plugin to the `plugins` array in `medusa-config.js`:
```js title="medusa-config.js"
const plugins = [
// ...
{
resolve: "medusa-plugin-strapi-ts",
options: {
strapi_protocol: process.env.STRAPI_PROTOCOL,
strapi_host: process.env.STRAPI_SERVER_HOSTNAME,
strapi_port: process.env.STRAPI_PORT,
strapi_secret: process.env.STRAPI_SECRET,
strapi_default_user: {
username: process.env.STRAPI_MEDUSA_USER,
password: process.env.STRAPI_MEDUSA_PASSWORD,
email: process.env.STRAPI_MEDUSA_EMAIL,
confirmed: true,
blocked: false,
provider: "local",
},
strapi_admin: {
username: process.env.STRAPI_SUPER_USERNAME,
password: process.env.STRAPI_SUPER_PASSWORD,
email: process.env.STRAPI_SUPER_USER_EMAIL,
},
auto_start: true,
},
},
]
```
### Strapi Plugin Options
1. `strapi_protocol`: The protocol of the Strapi server. If running locally, it should be `http`. Otherwise, it should be `https`.
2. `strapi_host`: the domain of the Strapi server. If running locally, use `127.0.0.1`.
3. `strapi_port`: the port that the Strapi server is running on, if any. If running locally, use `1337`.
4. `strapi_secret`: the same secret used for the `MEDUSA_STRAPI_SECRET` environment variable in the Strapi project.
5. `strapi_default_user`: The details of an existing user or a user to create in the Strapi backend that is used to update data in Strapi. Its an object accepting the following properties:
1. `username`: The users username.
2. `password`: The users password.
3. `email`: The users email.
4. `confirmed`: Whether the user is confirmed.
5. `blocked`: Whether the user is blocked.
6. `provider`: The name of the authentication provider.
6. `strapi_admin`: The details of the super admin. The super admin is only used to create the default user if it doesnt exist. Its an object accepting the following properties:
1. `username`: the super admins username. Its value is the same as that of the `SUPERUSER_USERNAME` environment variable in the Strapi project.
2. `password`: the super admins password. Its value is the same as that of the `SUPERUSER_PASSWORD` environment variable in the Strapi project.
3. `email`: the super admins email. Its value is the same as that of the `SUPERUSER_EMAIL` environment variable in the Strapi project.
7. `auto_start`: Whether to initialize the Strapi connection when Medusa starts. Disabling this may cause issues when syncing data from Medusa to Strapi.
Refer to the [plugins README](https://github.com/SGFGOV/medusa-strapi-repo/blob/development/packages/medusa-plugin-strapi-ts/README.md) for more options.
### Environment Variables
Make sure to add the necessary environment variables for the above options in `.env`:
```bash
STRAPI_PROTOCOL=http
STRAPI_SERVER_HOSTNAME=127.0.0.1
STRAPI_PORT=1337
STRAPI_SECRET=supersecret
STRAPI_MEDUSA_USER=medusa
STRAPI_MEDUSA_PASSWORD=supersecret
STRAPI_MEDUSA_EMAIL=admin@medusa-test.com
STRAPI_SUPER_USERNAME=SuperUser
STRAPI_SUPER_PASSWORD=MedusaStrapi1
STRAPI_SUPER_USER_EMAIL=support@medusa-commerce.com
```
---
## Test the Plugin
To test the integration between Medusa and Strapi, first, start the Strapi server by running the following command in the `medusa-strapi-repo/packages/medusa-strapi` directory:
```bash title="medusa-strapi-repo/packages/medusa-strapi" npm2yarn
npm run develop
```
Then, start the Medusa application:
```bash title="Medusa Backend" npm2yarn
npx medusa develop
```
If the connection to Strapi is successful, youll find the following message logged in your Medusa application with no errors:
```bash
info: Checking Strapi Health ,data:
debug: check-url: http://127.0.0.1:1337/_health ,data:
info: Strapi Subscriber Initialized
```
### Synced Entities
The Medusa and Strapi plugins support syncing the following Medusa data models:
- `Region`
- `Product`
- `ProductVariant`
- `ProductCollection`
- `ProductCategory`
### Two-Way Syncing
To test syncing data from Medusa to Strapi, try creating or updating a product either using the Medusa Admin or the [REST APIs](https://docs.medusajs.com/api/admin#products_postproducts). This triggers the associated event in Medusa, which makes the updates in Strapi.
<Note title="Tip">
Data is only synced to Strapi once you create or update them. So, if you have products in your Medusa application from before integrating Strapi, they wont be available by default in Strapi. Youll have to make updates to them, which triggers the update in Strapi.
</Note>
To test syncing data from Strapi to Medusa, try updating one of the products in the Strapi dashboard. If you check the products details in Medusa, theyre updated as expected.