docs: add documentation for migration generate cli tool (#8128)
* docs: add documentation for migration generate cli tool * changed migrations details in marketplace recipe * added generated oas files to action * vale + lint fixes * don't import from src in medusa-config.js * fix generate command in recipe * fix module name
This commit is contained in:
@@ -60,6 +60,8 @@ jobs:
|
||||
base: "develop"
|
||||
title: "chore(docs): Updated API Reference (v2)"
|
||||
labels: "type: chore"
|
||||
add-paths: www/apps/api-reference/specs
|
||||
add-paths: |
|
||||
www/apps/api-reference/specs
|
||||
www/utils/generated/oas-output
|
||||
branch: "docs/generate-api-ref"
|
||||
branch-suffix: "timestamp"
|
||||
@@ -33,10 +33,13 @@ To disable the authentication guard on custom routes under the `/admin` or `/sto
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/customers/me/custom/route.ts" highlights={[["15"]]} apiTesting testApiUrl="http://localhost:9000/store/customers/me/custom" testApiMethod="GET"
|
||||
```ts title="src/api/store/customers/me/custom/route.ts" highlights={[["12"]]} apiTesting testApiUrl="http://localhost:9000/store/customers/me/custom" testApiMethod="GET"
|
||||
import type { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
|
||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
res.json({
|
||||
message: "Hello",
|
||||
})
|
||||
@@ -55,7 +58,7 @@ You can access the logged-in customer’s ID in all API routes starting with `/s
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/store/customers/me/custom/route.ts" highlights={[["16", "", "Access the logged-in customer's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
```ts title="src/api/store/customers/me/custom/route.ts" highlights={[["17", "req.auth_context.actor_id", "Access the logged-in customer's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
@@ -89,7 +92,7 @@ You can access the logged-in admin user’s ID in all API Routes starting with `
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/api/admin/custom/route.ts" highlights={[["16", "req.user.userId", "Access the logged-in admin user's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
```ts title="src/api/admin/custom/route.ts" highlights={[["17", "req.auth_context.actor_id", "Access the logged-in admin user's ID."]]} collapsibleLines="1-7" expandButtonLabel="Show Imports"
|
||||
import type {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
@@ -125,12 +128,12 @@ For example:
|
||||
|
||||
export const highlights = [
|
||||
[
|
||||
"11",
|
||||
"7",
|
||||
"authenticate",
|
||||
"Only authenticated admin users can access routes starting with `/custom/admin`",
|
||||
],
|
||||
[
|
||||
"17",
|
||||
"11",
|
||||
"authenticate",
|
||||
"Only authenticated customers can access routes starting with `/custom/customers`",
|
||||
],
|
||||
|
||||
@@ -27,14 +27,14 @@ export const belongsHighlights = [
|
||||
// when creating an email
|
||||
const email = await helloModuleService.createEmail({
|
||||
// other properties...
|
||||
user_id: "123"
|
||||
user_id: "123",
|
||||
})
|
||||
|
||||
// when updating an email
|
||||
const email = await helloModuleService.updateEmail({
|
||||
id: "321",
|
||||
// other properties...
|
||||
user_id: "123"
|
||||
user_id: "123",
|
||||
})
|
||||
```
|
||||
|
||||
@@ -57,14 +57,14 @@ export const manyHighlights = [
|
||||
// when creating a product
|
||||
const product = await helloModuleService.createProduct({
|
||||
// other properties...
|
||||
order_ids: ["123", "321"]
|
||||
order_ids: ["123", "321"],
|
||||
})
|
||||
|
||||
// when updating an order
|
||||
const order = await helloModuleService.updateOrder({
|
||||
id: "321",
|
||||
// other properties...
|
||||
product_ids: ["123", "321"]
|
||||
product_ids: ["123", "321"],
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Write Migration`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this chapter, you'll learn how to create a migration and write it manually.
|
||||
|
||||
## What is a Migration?
|
||||
|
||||
A migration is a class created in a TypeScript or JavaScript file under a module's `migrations` directory. It has two methods:
|
||||
|
||||
- The `up` method reflects changes on the database.
|
||||
- The `down` method reverts the changes made in the `up` method.
|
||||
|
||||
---
|
||||
|
||||
## How to Write a Migration?
|
||||
|
||||
The Medusa CLI tool provides a [migrations generate](!resources!/medusa-cli#migrations-generate) command to generate a migration for the specified modules' data models.
|
||||
|
||||
Alternatively, you can manually create a migration file under the `migrations` directory of your module.
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/modules/hello/migrations/Migration20240429.ts"
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240702105919 extends Migration {
|
||||
|
||||
async up(): Promise<void> {
|
||||
this.addSql("create table if not exists \"my_custom\" (\"id\" text not null, \"name\" text not null, \"created_at\" timestamptz not null default now(), \"updated_at\" timestamptz not null default now(), \"deleted_at\" timestamptz null, constraint \"my_custom_pkey\" primary key (\"id\"));")
|
||||
}
|
||||
|
||||
async down(): Promise<void> {
|
||||
this.addSql("drop table if exists \"my_custom\" cascade;")
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The migration's file name should be of the format `Migration{YEAR}{MONTH}{DAY}.ts`. The migration class in the file extends the `Migration` class imported from `@mikro-orm/migrations`.
|
||||
|
||||
In the `up` and `down` method of the migration class, you use the `addSql` method provided by MikroORM's `Migration` class to run PostgreSQL syntax.
|
||||
|
||||
In the example above, the `up` method creates the table `my_custom`, and the `down` method drops the table if the migration is reverted.
|
||||
|
||||
---
|
||||
|
||||
## Run the Migration
|
||||
|
||||
To run your migration, run the following command:
|
||||
|
||||
```bash
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
This reflects the changes in the database as implemented in the migration's `up` method.
|
||||
@@ -111,7 +111,7 @@ import { defineLink } from "@medusajs/utils"
|
||||
export default defineLink(
|
||||
{
|
||||
linkable: HelloModule.linkable.myCustom,
|
||||
isList: true
|
||||
isList: true,
|
||||
},
|
||||
ProductModule.linkable.product
|
||||
)
|
||||
|
||||
@@ -89,7 +89,7 @@ const query = remoteQueryObjectFromString({
|
||||
fields: [
|
||||
"id",
|
||||
"name",
|
||||
"product.*"
|
||||
"product.*",
|
||||
],
|
||||
})
|
||||
```
|
||||
@@ -112,7 +112,7 @@ const query = remoteQueryObjectFromString({
|
||||
fields: [
|
||||
"id",
|
||||
"name",
|
||||
"products.*"
|
||||
"products.*",
|
||||
],
|
||||
})
|
||||
```
|
||||
|
||||
@@ -40,10 +40,10 @@ The first step in the workflow receives the product’s ID and the data to updat
|
||||
Create the file `src/workflows/update-product-erp/steps/update-product.ts` with the following content:
|
||||
|
||||
export const updateProductHighlights = [
|
||||
["13", "resolve", "Resolve the `ProductService` from the Medusa container."],
|
||||
["16", "previousProductData", "Retrieve the `previousProductData` to pass it to the compensation function."],
|
||||
["19", "updateProducts", "Update the product."],
|
||||
["39", "updateProducts", "Revert the product’s data using the `previousProductData` passed from the step to the compensation function."]
|
||||
["10", "resolve", "Resolve the `ProductService` from the Medusa container."],
|
||||
["13", "previousProductData", "Retrieve the `previousProductData` to pass it to the compensation function."],
|
||||
["16", "updateProducts", "Update the product."],
|
||||
["30", "updateProducts", "Revert the product’s data using the `previousProductData` passed from the step to the compensation function."]
|
||||
]
|
||||
|
||||
```ts title="src/workflows/update-product-erp/steps/update-product.ts" highlights={updateProductHighlights} collapsibleLines="1-9" expandButtonLabel="Show Imports"
|
||||
@@ -130,22 +130,22 @@ Create the file `src/workflows/update-product-erp/steps/update-erp.ts` with the
|
||||
|
||||
export const updateErpHighlights = [
|
||||
[
|
||||
"12",
|
||||
"9",
|
||||
"resolve",
|
||||
"Resolve the `erpModuleService` from the Medusa container.",
|
||||
],
|
||||
[
|
||||
"17",
|
||||
"14",
|
||||
"previousErpData",
|
||||
"Retrieve the `previousErpData` to pass it to the compensation function.",
|
||||
],
|
||||
[
|
||||
"21",
|
||||
"16",
|
||||
"updateProductErpData",
|
||||
"Update the product’s ERP data and return the data from the ERP system.",
|
||||
],
|
||||
[
|
||||
"37",
|
||||
"31",
|
||||
"updateProductErpData",
|
||||
"Revert the product's data in the ERP system to its previous state using the `previousErpData`.",
|
||||
],
|
||||
|
||||
@@ -27,7 +27,7 @@ export const highlights = [
|
||||
```ts highlights={highlights}
|
||||
import {
|
||||
createWorkflow,
|
||||
when
|
||||
when,
|
||||
} from "@medusajs/workflows-sdk"
|
||||
// step imports...
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ const step1 = createStep(
|
||||
// ...
|
||||
|
||||
return new StepResponse({
|
||||
myMap
|
||||
myMap,
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -146,7 +146,7 @@ const step1 = createStep(
|
||||
// ...
|
||||
|
||||
return new StepResponse({
|
||||
myObj
|
||||
myObj,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -22,17 +22,21 @@ Similarly to your custom module, a commerce module's main service is registered
|
||||
|
||||
For example, you saw this code snippet in the [Medusa container chapter](../medusa-container/page.mdx):
|
||||
|
||||
```ts highlights={[["13"]]}
|
||||
```ts highlights={[["10"]]}
|
||||
import type { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/utils"
|
||||
|
||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const productModuleService: IProductModuleService = req.scope.resolve(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
const [, count] = await productModuleService.listAndCount()
|
||||
const [, count] = await productModuleService
|
||||
.listAndCountProducts()
|
||||
|
||||
res.json({
|
||||
count,
|
||||
|
||||
@@ -17,7 +17,7 @@ A data model is a class that represents a table in the database. It's created in
|
||||
<Note title="Steps Summary">
|
||||
|
||||
1. Create a data model in a module.
|
||||
2. Create migration for the data model.
|
||||
2. Generate migration for the data model.
|
||||
4. Run migration to add the data model's table in the database.
|
||||
|
||||
</Note>
|
||||
@@ -44,53 +44,21 @@ You define a data model using the `model`'s `define` method. It accepts two para
|
||||
|
||||
The example above defines the data model `MyCustom` with the properties `id` and `name`.
|
||||
|
||||
### Create a Migration
|
||||
### Generate a Migration
|
||||
|
||||
A migration defines changes to be made in the database, such as create or update tables.
|
||||
|
||||
A migration is a class created in a TypeScript or JavaScript file under a module's `migrations` directory. It has two methods:
|
||||
To generate a migration for the data models in your module, run the following command:
|
||||
|
||||
- The `up` method reflects changes on the database.
|
||||
- The `down` method reverts the changes made in the `up` method.
|
||||
```bash
|
||||
npx medusa migrations generate helloModuleService
|
||||
```
|
||||
|
||||
<Details summaryContent="Generate Migration">
|
||||
To generate migrations:
|
||||
The `migrations generate` command of the Medusa CLI accepts one or more module names (for example, `helloModuleService`) to generate the migration for.
|
||||
|
||||
1. Create the file `src/modules/hello/migrations-config.ts` with the following content:
|
||||
The above command creates a migration file at the directory `src/modules/hello/migrations` similar to the following:
|
||||
|
||||
```ts
|
||||
import { defineMikroOrmCliConfig } from "@medusajs/utils"
|
||||
import path from "path"
|
||||
import MyCustom from "./models/my-custom"
|
||||
import { HELLO_MODULE } from "."
|
||||
|
||||
export default defineMikroOrmCliConfig(HELLO_MODULE, {
|
||||
entities: [MyCustom] as any[],
|
||||
migrations: {
|
||||
path: path.join(__dirname, "migrations"),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
2. Run the following command in the root directory of your Medusa application:
|
||||
|
||||
```bash
|
||||
npx cross-env MIKRO_ORM_CLI=./src/modules/hello/migrations-config.ts mikro-orm migration:create
|
||||
```
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Add this command as a script in `package.json` for easy usage in the future. Use this command whenever you want to generate a new migration in your module.
|
||||
|
||||
</Note>
|
||||
|
||||
After running the command, a migration file is generated under the `src/modules/hello/migrations` directory.
|
||||
|
||||
</Details>
|
||||
|
||||
For example:
|
||||
|
||||
```ts title="src/modules/migrations/Migration20240429090012.ts"
|
||||
```ts
|
||||
import { Migration } from "@mikro-orm/migrations"
|
||||
|
||||
export class Migration20240702105919 extends Migration {
|
||||
@@ -106,17 +74,11 @@ export class Migration20240702105919 extends Migration {
|
||||
}
|
||||
```
|
||||
|
||||
In the `up` method, you create the table `my_custom` and define its columns. In the `down` method, you drop the table.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
The queries performed in each of the methods use PostgreSQL syntax.
|
||||
|
||||
</Note>
|
||||
In the migration class, the `up` method creates the table `my_custom` and defines its columns. The `down` method drops the table.
|
||||
|
||||
### Run Migration
|
||||
|
||||
To reflect the changes in the migration, run the `migration` command:
|
||||
To reflect the changes in the generated migration file, run the `migration` command:
|
||||
|
||||
```bash
|
||||
npx medusa migrations run
|
||||
|
||||
@@ -81,9 +81,9 @@ The subscriber function accepts an object parameter with the property `container
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["10", "container", "Recieve the Medusa Container in the object parameter."],
|
||||
["13", "resolve", "Resolve the Product Module's main service."],
|
||||
["13", "ModuleRegistrationName.PRODUCT", "The module's registration name imported from `@medusajs/modules-sdk`."]
|
||||
["7", "container", "Recieve the Medusa Container in the object parameter."],
|
||||
["10", "resolve", "Resolve the Product Module's main service."],
|
||||
["10", "ModuleRegistrationName.PRODUCT", "The module's registration name imported from `@medusajs/modules-sdk`."]
|
||||
]
|
||||
|
||||
```ts title="src/subscribers/product-created.ts" highlights={highlights}
|
||||
|
||||
@@ -15,9 +15,9 @@ You use the Medusa container to resolve resources, such as services.
|
||||
For example, in a custom API route you can resolve any service registered in the Medusa application using the `scope.resolve` method of the `MedusaRequest` parameter:
|
||||
|
||||
export const highlights = [
|
||||
["13", "resolve", "Resolve the Product Module's main service."],
|
||||
["9", "resolve", "Resolve the Product Module's main service."],
|
||||
[
|
||||
"13",
|
||||
"10",
|
||||
"ModuleRegistrationName.PRODUCT",
|
||||
"The resource registration name imported from `@medusajs/modules-sdk`.",
|
||||
],
|
||||
@@ -28,7 +28,10 @@ import type { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { ModuleRegistrationName } from "@medusajs/utils"
|
||||
|
||||
export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const productModuleService: IProductModuleService = req.scope.resolve(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
)
|
||||
|
||||
@@ -70,21 +70,18 @@ The last step is to add the module in Medusa’s configurations.
|
||||
|
||||
In `medusa-config.js`, add a `modules` property and pass to it your custom module:
|
||||
|
||||
```js title="medusa-config.js" highlights={[["7", "HELLO_MODULE", "The key of the main service to be registered in the Medusa container."]]}
|
||||
import { HELLO_MODULE } from "./src/modules/hello"
|
||||
// ...
|
||||
|
||||
```js title="medusa-config.js" highlights={[["4", "helloModuleService", "The key of the main service to be registered in the Medusa container."]]}
|
||||
module.exports = defineConfig({
|
||||
// ...
|
||||
modules: {
|
||||
[HELLO_MODULE]: {
|
||||
"helloModuleService": {
|
||||
resolve: "./modules/hello",
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Its key (`helloModuleService` or `HELLO_MODULE`) is the name of the module’s main service. It will be registered in the Medusa container with that name. It should also be the same name passed as the first parameter to the `Module` function in the module's definition.
|
||||
Its key (`helloModuleService`) is the name of the module’s main service. It will be registered in the Medusa container with that name. It should also be the same name passed as the first parameter to the `Module` function in the module's definition.
|
||||
|
||||
Its value is an object having the `resolve` property, whose value is either a path to module's directory relative to `src`(it shouldn't include `src` in the path), or an `npm` package’s name.
|
||||
|
||||
|
||||
@@ -164,6 +164,10 @@ export const sidebar = sidebarAttachHrefCommonOptions(
|
||||
path: "/advanced-development/data-models/searchable-property",
|
||||
title: "Searchable Property",
|
||||
},
|
||||
{
|
||||
path: "/advanced-development/data-models/write-migration",
|
||||
title: "Write Migration",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -74,7 +74,7 @@ const orderReturn = await orderModuleService.createReturn({
|
||||
items: [{
|
||||
id: "orditem_123",
|
||||
quantity: 1,
|
||||
}]
|
||||
}],
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ const actions = await promotionModuleService.computeActions(
|
||||
items: lineItemAdjustments,
|
||||
shipping_methods: shippingMethodAdjustments,
|
||||
// TODO infer from cart or region
|
||||
currency_code: "usd"
|
||||
currency_code: "usd",
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
@@ -8,12 +8,6 @@ export const metadata = {
|
||||
|
||||
The Medusa CLI tool provides commands that facilitate your development.
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Medusa's CLI tool doesn't fully support Medusa v2 yet.
|
||||
|
||||
</Note>
|
||||
|
||||
<Note type="check">
|
||||
|
||||
- [Node.js v16+](https://nodejs.org/en/download)
|
||||
@@ -23,19 +17,14 @@ Medusa's CLI tool doesn't fully support Medusa v2 yet.
|
||||
|
||||
## Usage
|
||||
|
||||
In your Medusa application's directory, you can use the Medusa CLI tool using NPX. For example:
|
||||
In your Medusa application's directory, you can use the Medusa CLI tool using NPX.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
npx medusa --help
|
||||
```
|
||||
|
||||
Alternatively, you can install the CLI tool globally:
|
||||
|
||||
```bash npm2yarn
|
||||
npm install @medusajs/medusa-cli -g
|
||||
medusa --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
@@ -348,7 +337,7 @@ Run the latest migrations to reflect changes on the database.
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
### migration revert
|
||||
### migrations revert
|
||||
|
||||
Revert the last migrations ran on one or more modules.
|
||||
|
||||
@@ -375,7 +364,46 @@ npx medusa migrations revert <module_names...>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The name of one or more module, separated by spaces. For example, `helloModuleService`.
|
||||
The name of one or more module (separated by spaces) to rever their migrations. For example, `helloModuleService`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
Yes
|
||||
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table.Body>
|
||||
</Table>
|
||||
|
||||
### migrations generate
|
||||
|
||||
Generate a migration file for the latest changes in one or more modules.
|
||||
|
||||
```bash
|
||||
npx medusa migrations generate <module_names...>
|
||||
```
|
||||
|
||||
#### Arguments
|
||||
|
||||
<Table>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.HeaderCell>Argument</Table.HeaderCell>
|
||||
<Table.HeaderCell>Description</Table.HeaderCell>
|
||||
<Table.HeaderCell>Required</Table.HeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
|
||||
`module_names`
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
The name of one or more module (separated by spaces) to generate migrations for. For example, `helloModuleService`.
|
||||
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
|
||||
@@ -67,7 +67,7 @@ const Vendor = model.define("vendor", {
|
||||
handle: model.text(),
|
||||
name: model.text(),
|
||||
logo: model.text().nullable(),
|
||||
admins: model.hasMany(() => VendorAdmin)
|
||||
admins: model.hasMany(() => VendorAdmin),
|
||||
})
|
||||
|
||||
export default Vendor
|
||||
@@ -89,8 +89,8 @@ const VendorAdmin = model.define("vendor_admin", {
|
||||
last_name: model.text().nullable(),
|
||||
email: model.text().unique(),
|
||||
vendor: model.belongsTo(() => Vendor, {
|
||||
mappedBy: "admins"
|
||||
})
|
||||
mappedBy: "admins",
|
||||
}),
|
||||
})
|
||||
|
||||
export default VendorAdmin
|
||||
@@ -98,34 +98,6 @@ export default VendorAdmin
|
||||
|
||||
This creates a `VendorAdmin` data model, which represents an admin of a vendor.
|
||||
|
||||
### Generate Migrations
|
||||
|
||||
To create tables for the data models in the database, you need to generate migrations for them.
|
||||
|
||||
Create the file `src/modules/marketplace/migrations-config.ts` with the following content:
|
||||
|
||||
```ts
|
||||
import { defineMikroOrmCliConfig } from "@medusajs/utils"
|
||||
import path from "path"
|
||||
import Vendor from "./models/vendor"
|
||||
import VendorAdmin from "./models/vendor-admin"
|
||||
|
||||
export default defineMikroOrmCliConfig("marketplace", {
|
||||
entities: [Vendor, VendorAdmin] as any[],
|
||||
migrations: {
|
||||
path: path.join(__dirname, "migrations"),
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
Then, run the following command to generate the migration:
|
||||
|
||||
```bash
|
||||
npx cross-env MIKRO_ORM_CLI=./src/modules/marketplace/migrations-config.ts mikro-orm migration:create
|
||||
```
|
||||
|
||||
This generates a migration in the `src/modules/marketeplace/migrations` directory.
|
||||
|
||||
### Create Main Module Service
|
||||
|
||||
Next, create the main service of the module at `src/modules/marketplace/service.ts` with the following content:
|
||||
@@ -138,7 +110,7 @@ import { CreateVendorData, VendorData } from "./types"
|
||||
|
||||
class MarketplaceModuleService extends MedusaService({
|
||||
Vendor,
|
||||
VendorAdmin
|
||||
VendorAdmin,
|
||||
}) {
|
||||
}
|
||||
|
||||
@@ -158,7 +130,7 @@ import MarketplaceModuleService from "./service"
|
||||
export const MARKETPLACE_MODULE = "marketplaceModuleService"
|
||||
|
||||
export default Module(MARKETPLACE_MODULE, {
|
||||
service: MarketplaceModuleService
|
||||
service: MarketplaceModuleService,
|
||||
})
|
||||
```
|
||||
|
||||
@@ -167,7 +139,7 @@ export default Module(MARKETPLACE_MODULE, {
|
||||
Finally, add the module to the list of modules in `medusa-config.js`:
|
||||
|
||||
```ts title="medusa-config.js"
|
||||
import { MARKETPLACE_MODULE } from './src/modules/marketplace'
|
||||
import { MARKETPLACE_MODULE } from "./src/modules/marketplace"
|
||||
|
||||
// ...
|
||||
|
||||
@@ -177,10 +149,10 @@ module.exports = defineConfig({
|
||||
[MARKETPLACE_MODULE]: {
|
||||
resolve: "./modules/marketplace",
|
||||
definition: {
|
||||
isQueryable: true
|
||||
}
|
||||
}
|
||||
}
|
||||
isQueryable: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
@@ -212,7 +184,7 @@ export default defineLink(
|
||||
MarketplaceModule.linkable.vendor,
|
||||
{
|
||||
linkable: ProductModule.linkable.product,
|
||||
isList: true
|
||||
isList: true,
|
||||
}
|
||||
)
|
||||
```
|
||||
@@ -230,7 +202,7 @@ export default defineLink(
|
||||
MarketplaceModule.linkable.vendor,
|
||||
{
|
||||
linkable: OrderModule.linkable.order,
|
||||
isList: true
|
||||
isList: true,
|
||||
}
|
||||
)
|
||||
```
|
||||
@@ -243,9 +215,17 @@ This adds a list link between the `Vendor` and `Order` data models, indicating t
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Run Migrations
|
||||
## Step 3: Generate and Run Migrations
|
||||
|
||||
To reflect the module’s data models and the module links in the database, run the following command:
|
||||
To create tables for the marketplace data models in the database, start by generating the migrations for the Marketplace Module with the following command:
|
||||
|
||||
```bash
|
||||
npx medusa migrations generate marketplaceModuleService
|
||||
```
|
||||
|
||||
This generates a migration in the `src/modules/marketeplace/migrations` directory.
|
||||
|
||||
Then, to reflect the migration and the module links in the database, run the following command:
|
||||
|
||||
```bash
|
||||
npx medusa migrations run
|
||||
@@ -372,12 +352,12 @@ export const vendorRouteSchemaHighlights = [
|
||||
```ts title="src/api/vendors/route.ts" highlights={vendorRouteSchemaHighlights}
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
import { z } from "zod"
|
||||
import MarketplaceModuleService from "../../modules/marketplace/service";
|
||||
import createVendorAdminWorkflow from "../../workflows/marketplace/create-vendor-admin";
|
||||
import MarketplaceModuleService from "../../modules/marketplace/service"
|
||||
import createVendorAdminWorkflow from "../../workflows/marketplace/create-vendor-admin"
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string(),
|
||||
@@ -386,8 +366,8 @@ const schema = z.object({
|
||||
admin: z.object({
|
||||
email: z.string(),
|
||||
first_name: z.string().optional(),
|
||||
last_name: z.string().optional()
|
||||
}).strict()
|
||||
last_name: z.string().optional(),
|
||||
}).strict(),
|
||||
}).strict()
|
||||
|
||||
type RequestBody = {
|
||||
@@ -441,15 +421,15 @@ export const POST = async (
|
||||
input: {
|
||||
admin: {
|
||||
...admin,
|
||||
vendor_id: vendor[0].id
|
||||
vendor_id: vendor[0].id,
|
||||
},
|
||||
authIdentityId: req.auth_context.auth_identity_id,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
// retrieve vendor again with admins
|
||||
vendor = await marketplaceModuleService.retrieveVendor(vendor[0].id, {
|
||||
relations: ["admins"]
|
||||
relations: ["admins"],
|
||||
})
|
||||
|
||||
res.json({
|
||||
@@ -482,7 +462,7 @@ export const config: MiddlewaresConfig = {
|
||||
matcher: "/vendors/*",
|
||||
middlewares: [
|
||||
authenticate("vendor", ["session", "bearer"]),
|
||||
]
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -571,12 +551,12 @@ export const retrieveProductHighlights = [
|
||||
]
|
||||
|
||||
```ts title="src/api/vendors/products/route.ts" highlights={retrieveProductHighlights}
|
||||
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/medusa";
|
||||
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import {
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import MarketplaceModuleService from "../../../modules/marketplace/service";
|
||||
import { MARKETPLACE_MODULE } from "../../../modules/marketplace";
|
||||
import MarketplaceModuleService from "../../../modules/marketplace/service"
|
||||
import { MARKETPLACE_MODULE } from "../../../modules/marketplace"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
@@ -589,7 +569,7 @@ export const GET = async (
|
||||
const vendorAdmin = await marketplaceModuleService.retrieveVendorAdmin(
|
||||
req.auth_context.actor_id,
|
||||
{
|
||||
relations: ["vendor"]
|
||||
relations: ["vendor"],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -598,15 +578,15 @@ export const GET = async (
|
||||
fields: ["products.*"],
|
||||
variables: {
|
||||
filters: {
|
||||
id: [vendorAdmin.vendor.id]
|
||||
}
|
||||
}
|
||||
id: [vendorAdmin.vendor.id],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const result = await remoteQuery(query)
|
||||
|
||||
res.json({
|
||||
products: result[0].products
|
||||
products: result[0].products,
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -626,11 +606,11 @@ import { createProductsWorkflow } from "@medusajs/core-flows"
|
||||
import {
|
||||
CreateProductWorkflowInputDTO,
|
||||
IProductModuleService,
|
||||
ISalesChannelModuleService
|
||||
ISalesChannelModuleService,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
Modules,
|
||||
ModuleRegistrationName
|
||||
ModuleRegistrationName,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
// GET method...
|
||||
@@ -655,7 +635,7 @@ export const POST = async (
|
||||
const vendorAdmin = await marketplaceModuleService.retrieveVendorAdmin(
|
||||
req.auth_context.actor_id,
|
||||
{
|
||||
relations: ["vendor"]
|
||||
relations: ["vendor"],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -678,19 +658,19 @@ const { result } = await createProductsWorkflow(req.scope)
|
||||
input: {
|
||||
products: [{
|
||||
...req.body,
|
||||
sales_channels: salesChannels
|
||||
}]
|
||||
}
|
||||
sales_channels: salesChannels,
|
||||
}],
|
||||
},
|
||||
})
|
||||
|
||||
// link product to vendor
|
||||
await remoteLink.create({
|
||||
[MARKETPLACE_MODULE]: {
|
||||
vendor_id: vendorAdmin.vendor.id
|
||||
vendor_id: vendorAdmin.vendor.id,
|
||||
},
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: result[0].id
|
||||
}
|
||||
product_id: result[0].id,
|
||||
},
|
||||
})
|
||||
|
||||
// retrieve product again
|
||||
@@ -699,7 +679,7 @@ const product = await productModuleService.retrieveProduct(
|
||||
)
|
||||
|
||||
res.json({
|
||||
product
|
||||
product,
|
||||
})
|
||||
```
|
||||
|
||||
@@ -727,8 +707,8 @@ export const config: MiddlewaresConfig = {
|
||||
middlewares: [
|
||||
authenticate("vendor", ["session", "bearer"]),
|
||||
validateAndTransformBody(AdminCreateProduct),
|
||||
]
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
@@ -823,11 +803,11 @@ const retrieveCartStep = createStep(
|
||||
.resolve(ModuleRegistrationName.CART)
|
||||
|
||||
const cart = await cartModuleService.retrieveCart(cart_id, {
|
||||
relations: ["items"]
|
||||
relations: ["items"],
|
||||
})
|
||||
|
||||
return new StepResponse({
|
||||
cart
|
||||
cart,
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -862,12 +842,12 @@ const createParentOrderStep = createStep(
|
||||
const { result } = await completeCartWorkflow(container)
|
||||
.run({
|
||||
input: {
|
||||
id: cart_id
|
||||
}
|
||||
id: cart_id,
|
||||
},
|
||||
})
|
||||
|
||||
return new StepResponse({
|
||||
order: result
|
||||
order: result,
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -906,9 +886,9 @@ const groupVendorItemsStep = createStep(
|
||||
fields: ["vendor.*"],
|
||||
variables: {
|
||||
filters: {
|
||||
id: [item.product_id]
|
||||
}
|
||||
}
|
||||
id: [item.product_id],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const result = await remoteQuery(query)
|
||||
@@ -920,12 +900,12 @@ const groupVendorItemsStep = createStep(
|
||||
}
|
||||
vendorsItems[vendorId] = [
|
||||
...(vendorsItems[vendorId] || []),
|
||||
item
|
||||
item,
|
||||
]
|
||||
}))
|
||||
|
||||
return new StepResponse({
|
||||
vendorsItems
|
||||
vendorsItems,
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -973,7 +953,7 @@ const createVendorOrdersStep = createStep(
|
||||
const vendorIds = Object.keys(vendorsItems)
|
||||
if (vendorIds.length === 0) {
|
||||
return new StepResponse({
|
||||
orders: []
|
||||
orders: [],
|
||||
})
|
||||
}
|
||||
const remoteLink = container.resolve("remoteLink")
|
||||
@@ -1005,20 +985,20 @@ if (isOnlyOneVendorOrder) {
|
||||
// link the parent order to the vendor instead of creating child orders
|
||||
await remoteLink.create({
|
||||
[MARKETPLACE_MODULE]: {
|
||||
vendor_id: vendorId
|
||||
vendor_id: vendorId,
|
||||
},
|
||||
[Modules.ORDER]: {
|
||||
order_id: parentOrder.id
|
||||
}
|
||||
order_id: parentOrder.id,
|
||||
},
|
||||
})
|
||||
|
||||
return new StepResponse({
|
||||
orders: [
|
||||
{
|
||||
...parentOrder,
|
||||
vendor
|
||||
}
|
||||
]
|
||||
vendor,
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1050,7 +1030,7 @@ await Promise.all(
|
||||
input: {
|
||||
items,
|
||||
metadata: {
|
||||
parent_order_id: parentOrder.id
|
||||
parent_order_id: parentOrder.id,
|
||||
},
|
||||
// use info from parent
|
||||
region_id: parentOrder.region_id,
|
||||
@@ -1074,37 +1054,37 @@ await Promise.all(
|
||||
rate: taxLine.rate,
|
||||
provider_id: taxLine.provider_id,
|
||||
tax_rate_id: taxLine.tax_rate_id,
|
||||
description: taxLine.description
|
||||
description: taxLine.description,
|
||||
})),
|
||||
adjustments: shippingMethod.adjustments.map((adjustment) => ({
|
||||
code: adjustment.code,
|
||||
amount: adjustment.amount,
|
||||
description: adjustment.description,
|
||||
promotion_id: adjustment.promotion_id,
|
||||
provider_id: adjustment.provider_id
|
||||
}))
|
||||
provider_id: adjustment.provider_id,
|
||||
})),
|
||||
})),
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
await remoteLink.create({
|
||||
[MARKETPLACE_MODULE]: {
|
||||
vendor_id: vendorId
|
||||
vendor_id: vendorId,
|
||||
},
|
||||
[Modules.ORDER]: {
|
||||
order_id: childOrder.id
|
||||
}
|
||||
order_id: childOrder.id,
|
||||
},
|
||||
})
|
||||
|
||||
createdOrders.push({
|
||||
...childOrder,
|
||||
vendor
|
||||
vendor,
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
return new StepResponse({
|
||||
orders: createdOrders
|
||||
orders: createdOrders,
|
||||
})
|
||||
```
|
||||
|
||||
@@ -1150,7 +1130,7 @@ const createVendorOrdersWorkflow = createWorkflow<
|
||||
|
||||
const { vendorsItems } = groupVendorItemsStep(
|
||||
transform({
|
||||
cart
|
||||
cart,
|
||||
},
|
||||
(data) => data
|
||||
)
|
||||
@@ -1159,12 +1139,12 @@ const createVendorOrdersWorkflow = createWorkflow<
|
||||
const { orders } = createVendorOrdersStep(
|
||||
transform({
|
||||
order,
|
||||
vendorsItems
|
||||
vendorsItems,
|
||||
},
|
||||
(data) => {
|
||||
return {
|
||||
parentOrder: data.order,
|
||||
vendorsItems: data.vendorsItems
|
||||
vendorsItems: data.vendorsItems,
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -1172,11 +1152,11 @@ const createVendorOrdersWorkflow = createWorkflow<
|
||||
|
||||
return transform({
|
||||
order,
|
||||
orders
|
||||
orders,
|
||||
},
|
||||
(data) => ({
|
||||
parent_order: data.order,
|
||||
vendor_orders: data.orders
|
||||
vendor_orders: data.orders,
|
||||
})
|
||||
)
|
||||
}
|
||||
@@ -1196,9 +1176,9 @@ Create the file `src/api/store/carts/[id]/complete/route.ts` with the following
|
||||
```ts title="src/api/store/carts/[id]/complete/route.ts"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse
|
||||
} from "@medusajs/medusa";
|
||||
import createVendorOrdersWorkflow from "../../../../../workflows/marketplace/create-vendor-orders";
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
import createVendorOrdersWorkflow from "../../../../../workflows/marketplace/create-vendor-orders"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
@@ -1209,13 +1189,13 @@ export const POST = async (
|
||||
const { result } = await createVendorOrdersWorkflow(req.scope)
|
||||
.run({
|
||||
input: {
|
||||
cart_id: cartId
|
||||
}
|
||||
cart_id: cartId,
|
||||
},
|
||||
})
|
||||
|
||||
res.json({
|
||||
type: "order",
|
||||
order: result.parent_order
|
||||
order: result.parent_order,
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -1241,11 +1221,11 @@ export const getOrderHighlights = [
|
||||
]
|
||||
|
||||
```ts title="src/api/vendors/orders/route.ts" highlights={getOrderHighlights} collapsibleLines="1-6" expandMoreLabel="Show Imports"
|
||||
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/medusa";
|
||||
import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/medusa"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
import { getOrdersListWorkflow } from "@medusajs/core-flows"
|
||||
import MarketplaceModuleService from "../../../modules/marketplace/service";
|
||||
import { MARKETPLACE_MODULE } from "../../../modules/marketplace";
|
||||
import MarketplaceModuleService from "../../../modules/marketplace/service"
|
||||
import { MARKETPLACE_MODULE } from "../../../modules/marketplace"
|
||||
|
||||
export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
@@ -1258,7 +1238,7 @@ export const GET = async (
|
||||
const vendorAdmin = await marketplaceModuleService.retrieveVendorAdmin(
|
||||
req.auth_context.actor_id,
|
||||
{
|
||||
relations: ["vendor"]
|
||||
relations: ["vendor"],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1267,9 +1247,9 @@ export const GET = async (
|
||||
fields: ["orders.*"],
|
||||
variables: {
|
||||
filters: {
|
||||
id: [vendorAdmin.vendor.id]
|
||||
}
|
||||
}
|
||||
id: [vendorAdmin.vendor.id],
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const result = await remoteQuery(query)
|
||||
@@ -1295,14 +1275,14 @@ export const GET = async (
|
||||
],
|
||||
variables: {
|
||||
filters: {
|
||||
id: result[0].orders.map((order) => order.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
id: result[0].orders.map((order) => order.id),
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
res.json({
|
||||
orders
|
||||
orders,
|
||||
})
|
||||
}
|
||||
```
|
||||
@@ -1343,7 +1323,7 @@ export const config: MiddlewaresConfig = {
|
||||
authenticate("vendor", ["session", "bearer"]),
|
||||
],
|
||||
},
|
||||
]
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user