docs: update docusaurus to v3 (#5625)
* update dependencies * update onboarding mdx * fixes for mdx issues * fixes for mdx compatibility * resolve mdx errors * fixes in reference * fix check errors * revert change in vale action * fix node version in action * fix summary in markdown
This commit is contained in:
@@ -266,62 +266,60 @@ Medusa allows you to create custom API Routes exposed as REST APIs.
|
||||
}
|
||||
}} />
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Example Implementation
|
||||
</summary>
|
||||
<Details>
|
||||
<Summary>Example Implementation</Summary>
|
||||
|
||||
For example, create the following API Route that allows you to check the customer’s group and whether it has the `is_b2b` flag enabled:
|
||||
For example, create the following API Route that allows you to check the customer’s group and whether it has the `is_b2b` flag enabled:
|
||||
|
||||
```ts title=src/api/store/customers/is-b2b/route.ts
|
||||
import type {
|
||||
CustomerService,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const customerService: CustomerService = req.scope.resolve(
|
||||
"customerService"
|
||||
)
|
||||
|
||||
const customer = await customerService
|
||||
.retrieve(req.user.customer_id, {
|
||||
relations: ["groups"],
|
||||
})
|
||||
|
||||
const is_b2b = customer.groups.some(
|
||||
(group) => group.metadata.is_b2b === "true"
|
||||
)
|
||||
|
||||
```ts title=src/api/store/customers/is-b2b/route.ts
|
||||
import type {
|
||||
CustomerService,
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const customerService: CustomerService = req.scope.resolve(
|
||||
"customerService"
|
||||
)
|
||||
|
||||
const customer = await customerService
|
||||
.retrieve(req.user.customer_id, {
|
||||
relations: ["groups"],
|
||||
return res.json({
|
||||
is_b2b,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
const is_b2b = customer.groups.some(
|
||||
(group) => group.metadata.is_b2b === "true"
|
||||
)
|
||||
|
||||
return res.json({
|
||||
is_b2b,
|
||||
})
|
||||
}
|
||||
```
|
||||
Then add the `requireCustomerAuthentication` middleware in `src/api/middlewares.ts` that ensures only authenticated customer can access this API Route:
|
||||
|
||||
Then add the `requireCustomerAuthentication` middleware in `src/api/middlewares.ts` that ensures only authenticated customer can access this API Route:
|
||||
```ts title=src/api/middlewares.ts
|
||||
import {
|
||||
requireCustomerAuthentication,
|
||||
type MiddlewaresConfig,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
```ts title=src/api/middlewares.ts
|
||||
import {
|
||||
requireCustomerAuthentication,
|
||||
type MiddlewaresConfig,
|
||||
} from "@medusajs/medusa"
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
matcher: "/store/customers/is-b2b",
|
||||
middlewares: [requireCustomerAuthentication()],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
routes: [
|
||||
{
|
||||
matcher: "/store/customers/is-b2b",
|
||||
middlewares: [requireCustomerAuthentication()],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
</Details>
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -100,85 +100,83 @@ Medusa also provides official notification plugins that integrate with third-par
|
||||
},
|
||||
]} />
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Example: Sending an email for new notes
|
||||
</summary>
|
||||
|
||||
Here’s an example of a subscriber that uses the SendGrid plugin to send an email to the customer when the order has a new note:
|
||||
<Details>
|
||||
<Summary>Example: Sending an email for new notes</Summary>
|
||||
|
||||
```ts title=src/subscribers/new-note.ts
|
||||
import {
|
||||
EventBusService,
|
||||
NoteService,
|
||||
OrderService,
|
||||
} from "@medusajs/medusa"
|
||||
Here’s an example of a subscriber that uses the SendGrid plugin to send an email to the customer when the order has a new note:
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
sendgridService: any
|
||||
noteService: NoteService
|
||||
orderService: OrderService
|
||||
}
|
||||
```ts title=src/subscribers/new-note.ts
|
||||
import {
|
||||
EventBusService,
|
||||
NoteService,
|
||||
OrderService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
class NewNoteSubscriber {
|
||||
protected sendGridService_: any
|
||||
protected noteService_: NoteService
|
||||
protected orderService_: OrderService
|
||||
|
||||
constructor({
|
||||
eventBusService,
|
||||
sendgridService,
|
||||
noteService,
|
||||
orderService,
|
||||
}: InjectedDependencies) {
|
||||
this.noteService_ = noteService
|
||||
this.orderService_ = orderService
|
||||
this.sendGridService_ = sendgridService
|
||||
eventBusService.subscribe(
|
||||
"note.created",
|
||||
this.handleNoteCreated
|
||||
)
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
sendgridService: any
|
||||
noteService: NoteService
|
||||
orderService: OrderService
|
||||
}
|
||||
|
||||
handleNoteCreated = async (data) => {
|
||||
// retrieve note by id
|
||||
const note = await this.noteService_.retrieve(data.id, {
|
||||
relations: ["author"],
|
||||
})
|
||||
class NewNoteSubscriber {
|
||||
protected sendGridService_: any
|
||||
protected noteService_: NoteService
|
||||
protected orderService_: OrderService
|
||||
|
||||
if (!note || note.resource_type !== "order") {
|
||||
return
|
||||
constructor({
|
||||
eventBusService,
|
||||
sendgridService,
|
||||
noteService,
|
||||
orderService,
|
||||
}: InjectedDependencies) {
|
||||
this.noteService_ = noteService
|
||||
this.orderService_ = orderService
|
||||
this.sendGridService_ = sendgridService
|
||||
eventBusService.subscribe(
|
||||
"note.created",
|
||||
this.handleNoteCreated
|
||||
)
|
||||
}
|
||||
|
||||
// retrieve note's order
|
||||
const order = await this.orderService_.retrieve(
|
||||
note.resource_id
|
||||
)
|
||||
handleNoteCreated = async (data) => {
|
||||
// retrieve note by id
|
||||
const note = await this.noteService_.retrieve(data.id, {
|
||||
relations: ["author"],
|
||||
})
|
||||
|
||||
if (!order) {
|
||||
return
|
||||
if (!note || note.resource_type !== "order") {
|
||||
return
|
||||
}
|
||||
|
||||
// retrieve note's order
|
||||
const order = await this.orderService_.retrieve(
|
||||
note.resource_id
|
||||
)
|
||||
|
||||
if (!order) {
|
||||
return
|
||||
}
|
||||
|
||||
this.sendGridService_.sendEmail({
|
||||
templateId: "order-update",
|
||||
from: "hello@medusajs.com",
|
||||
to: order.email,
|
||||
dynamic_template_data: {
|
||||
// any data necessary for your template...
|
||||
note_text: note.value,
|
||||
note_author: note.author.first_name,
|
||||
note_date: note.created_at,
|
||||
order_id: order.display_id,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
this.sendGridService_.sendEmail({
|
||||
templateId: "order-update",
|
||||
from: "hello@medusajs.com",
|
||||
to: order.email,
|
||||
dynamic_template_data: {
|
||||
// any data necessary for your template...
|
||||
note_text: note.value,
|
||||
note_author: note.author.first_name,
|
||||
note_date: note.created_at,
|
||||
order_id: order.display_id,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default NewNoteSubscriber
|
||||
```
|
||||
export default NewNoteSubscriber
|
||||
```
|
||||
|
||||
</details>
|
||||
</Details>
|
||||
|
||||
---
|
||||
|
||||
@@ -198,75 +196,73 @@ You can implement automatic synchronization in Medusa using scheduled jobs. A sc
|
||||
}
|
||||
}} />
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Example: Synchronizing products with a third-party service
|
||||
</summary>
|
||||
<Details>
|
||||
<Summary>Example: Synchronizing products with a third-party service</Summary>
|
||||
|
||||
Here’s an example of synchronizing products with a third party service using a [loader](../development/loaders/create.md):
|
||||
Here’s an example of synchronizing products with a third party service using a [loader](../development/loaders/create.md):
|
||||
|
||||
```ts title=src/loaders/sync-products.ts
|
||||
import {
|
||||
Logger,
|
||||
ProductService,
|
||||
StoreService,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
ProductSelector,
|
||||
} from "@medusajs/medusa/dist/types/product"
|
||||
import { AwilixContainer } from "awilix"
|
||||
```ts title=src/loaders/sync-products.ts
|
||||
import {
|
||||
Logger,
|
||||
ProductService,
|
||||
StoreService,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
ProductSelector,
|
||||
} from "@medusajs/medusa/dist/types/product"
|
||||
import { AwilixContainer } from "awilix"
|
||||
|
||||
export default async (
|
||||
container: AwilixContainer,
|
||||
config: Record<string, unknown>
|
||||
): Promise<void> => {
|
||||
const logger = container.resolve<Logger>("logger")
|
||||
logger.info("Synchronizing products...")
|
||||
const productService = container.resolve<ProductService>(
|
||||
"productService"
|
||||
)
|
||||
const storeService = container.resolve<StoreService>(
|
||||
"storeService"
|
||||
)
|
||||
// retrieve store to get last sync date
|
||||
const store = await storeService.retrieve()
|
||||
export default async (
|
||||
container: AwilixContainer,
|
||||
config: Record<string, unknown>
|
||||
): Promise<void> => {
|
||||
const logger = container.resolve<Logger>("logger")
|
||||
logger.info("Synchronizing products...")
|
||||
const productService = container.resolve<ProductService>(
|
||||
"productService"
|
||||
)
|
||||
const storeService = container.resolve<StoreService>(
|
||||
"storeService"
|
||||
)
|
||||
// retrieve store to get last sync date
|
||||
const store = await storeService.retrieve()
|
||||
|
||||
const productFilters: ProductSelector = {}
|
||||
const productFilters: ProductSelector = {}
|
||||
|
||||
if (store.metadata.last_sync_date) {
|
||||
productFilters.updated_at = {
|
||||
gt: new Date(
|
||||
store.metadata.last_sync_date as string
|
||||
),
|
||||
if (store.metadata.last_sync_date) {
|
||||
productFilters.updated_at = {
|
||||
gt: new Date(
|
||||
store.metadata.last_sync_date as string
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
const updatedProducts = await productService.list(
|
||||
productFilters
|
||||
)
|
||||
|
||||
updatedProducts.forEach((product) => {
|
||||
// assuming client is an initialized connection
|
||||
// with a third-party service
|
||||
client.sync(product)
|
||||
})
|
||||
|
||||
await storeService.update({
|
||||
metadata: {
|
||||
last_sync_date: new Date(),
|
||||
},
|
||||
})
|
||||
|
||||
logger.info("Finish synchronizing products")
|
||||
}
|
||||
```
|
||||
|
||||
const updatedProducts = await productService.list(
|
||||
productFilters
|
||||
)
|
||||
|
||||
updatedProducts.forEach((product) => {
|
||||
// assuming client is an initialized connection
|
||||
// with a third-party service
|
||||
client.sync(product)
|
||||
})
|
||||
Notice that here it’s assumed that:
|
||||
|
||||
await storeService.update({
|
||||
metadata: {
|
||||
last_sync_date: new Date(),
|
||||
},
|
||||
})
|
||||
|
||||
logger.info("Finish synchronizing products")
|
||||
}
|
||||
```
|
||||
1. The last update date is stored in the `Store`'s metadata object. You can instead use a custom entity to handle this.
|
||||
2. The connection to the third-party service is assumed to be available and handled within the `client` variable.
|
||||
|
||||
Notice that here it’s assumed that:
|
||||
|
||||
1. The last update date is stored in the `Store`'s metadata object. You can instead use a custom entity to handle this.
|
||||
2. The connection to the third-party service is assumed to be available and handled within the `client` variable.
|
||||
|
||||
</details>
|
||||
</Details>
|
||||
|
||||
---
|
||||
|
||||
@@ -419,88 +415,86 @@ For example, if you're grouping customers with over twenty orders, you can use a
|
||||
},
|
||||
]} />
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Example: Add customer to VIP group
|
||||
</summary>
|
||||
<Details>
|
||||
<Summary>Example: Add customer to VIP group</Summary>
|
||||
|
||||
Here’s an example of a subscriber that listens to the `order.placed` event and checks if the customer should be added to the VIP customer group based on their number of orders:
|
||||
Here’s an example of a subscriber that listens to the `order.placed` event and checks if the customer should be added to the VIP customer group based on their number of orders:
|
||||
|
||||
```ts title=src/subscribers/add-custom-to-vip.ts
|
||||
import {
|
||||
CustomerGroupService,
|
||||
CustomerService,
|
||||
EventBusService,
|
||||
OrderService,
|
||||
} from "@medusajs/medusa"
|
||||
```ts title=src/subscribers/add-custom-to-vip.ts
|
||||
import {
|
||||
CustomerGroupService,
|
||||
CustomerService,
|
||||
EventBusService,
|
||||
OrderService,
|
||||
} from "@medusajs/medusa"
|
||||
|
||||
type InjectedDependencies = {
|
||||
orderService: OrderService
|
||||
customerService: CustomerService
|
||||
customerGroupService: CustomerGroupService
|
||||
eventBusService: EventBusService
|
||||
}
|
||||
|
||||
class AddCustomerToVipSubscriber {
|
||||
protected orderService_: OrderService
|
||||
protected customerService_: CustomerService
|
||||
protected customerGroupService_: CustomerGroupService
|
||||
|
||||
constructor({
|
||||
orderService,
|
||||
customerService,
|
||||
customerGroupService,
|
||||
eventBusService,
|
||||
}: InjectedDependencies) {
|
||||
this.orderService_ = orderService
|
||||
this.customerService_ = customerService
|
||||
this.customerGroupService_ = customerGroupService
|
||||
eventBusService.subscribe(
|
||||
"order.placed",
|
||||
this.handleOrderPlaced
|
||||
)
|
||||
type InjectedDependencies = {
|
||||
orderService: OrderService
|
||||
customerService: CustomerService
|
||||
customerGroupService: CustomerGroupService
|
||||
eventBusService: EventBusService
|
||||
}
|
||||
|
||||
handleOrderPlaced = async ({ id }) => {
|
||||
// check if VIP group exists
|
||||
const vipGroup = await this.customerGroupService_.list({
|
||||
name: "VIP",
|
||||
}, {
|
||||
relations: ["customers"],
|
||||
})
|
||||
if (!vipGroup.length) {
|
||||
return
|
||||
}
|
||||
class AddCustomerToVipSubscriber {
|
||||
protected orderService_: OrderService
|
||||
protected customerService_: CustomerService
|
||||
protected customerGroupService_: CustomerGroupService
|
||||
|
||||
// retrieve order and its customer
|
||||
const order = await this.orderService_.retrieve(id)
|
||||
|
||||
if (!order || !order.customer_id ||
|
||||
vipGroup[0].customers.find(
|
||||
(customer) => customer.id === order.customer_id
|
||||
) !== undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
// retrieve orders of this customer
|
||||
const [, count] = await this.orderService_.listAndCount({
|
||||
customer_id: order.customer_id,
|
||||
})
|
||||
|
||||
if (count >= 20) {
|
||||
// add customer to VIP group
|
||||
this.customerGroupService_.addCustomers(
|
||||
vipGroup[0].id,
|
||||
order.customer_id
|
||||
constructor({
|
||||
orderService,
|
||||
customerService,
|
||||
customerGroupService,
|
||||
eventBusService,
|
||||
}: InjectedDependencies) {
|
||||
this.orderService_ = orderService
|
||||
this.customerService_ = customerService
|
||||
this.customerGroupService_ = customerGroupService
|
||||
eventBusService.subscribe(
|
||||
"order.placed",
|
||||
this.handleOrderPlaced
|
||||
)
|
||||
}
|
||||
|
||||
handleOrderPlaced = async ({ id }) => {
|
||||
// check if VIP group exists
|
||||
const vipGroup = await this.customerGroupService_.list({
|
||||
name: "VIP",
|
||||
}, {
|
||||
relations: ["customers"],
|
||||
})
|
||||
if (!vipGroup.length) {
|
||||
return
|
||||
}
|
||||
|
||||
// retrieve order and its customer
|
||||
const order = await this.orderService_.retrieve(id)
|
||||
|
||||
if (!order || !order.customer_id ||
|
||||
vipGroup[0].customers.find(
|
||||
(customer) => customer.id === order.customer_id
|
||||
) !== undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
// retrieve orders of this customer
|
||||
const [, count] = await this.orderService_.listAndCount({
|
||||
customer_id: order.customer_id,
|
||||
})
|
||||
|
||||
if (count >= 20) {
|
||||
// add customer to VIP group
|
||||
this.customerGroupService_.addCustomers(
|
||||
vipGroup[0].id,
|
||||
order.customer_id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AddCustomerToVipSubscriber
|
||||
```
|
||||
export default AddCustomerToVipSubscriber
|
||||
```
|
||||
|
||||
</details>
|
||||
</Details>
|
||||
|
||||
|
||||
---
|
||||
@@ -543,105 +537,103 @@ You can alternatively have a Scheduled Job that checks if the number of new prod
|
||||
},
|
||||
]} />
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Example: Sending a newsletter email after adding ten products
|
||||
</summary>
|
||||
<Details>
|
||||
<Summary>Example: Sending a newsletter email after adding ten products</Summary>
|
||||
|
||||
Here’s an example of listening to the `product.created` event in a subscriber and send a newsletter if the condition is met:
|
||||
Here’s an example of listening to the `product.created` event in a subscriber and send a newsletter if the condition is met:
|
||||
|
||||
```ts title=src/subscribers/send-products-newsletter.ts
|
||||
import {
|
||||
CustomerService,
|
||||
EventBusService,
|
||||
NoteService,
|
||||
OrderService,
|
||||
ProductService,
|
||||
StoreService,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
ProductSelector,
|
||||
} from "@medusajs/medusa/dist/types/product"
|
||||
```ts title=src/subscribers/send-products-newsletter.ts
|
||||
import {
|
||||
CustomerService,
|
||||
EventBusService,
|
||||
NoteService,
|
||||
OrderService,
|
||||
ProductService,
|
||||
StoreService,
|
||||
} from "@medusajs/medusa"
|
||||
import {
|
||||
ProductSelector,
|
||||
} from "@medusajs/medusa/dist/types/product"
|
||||
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
sendgridService: any
|
||||
productService: ProductService
|
||||
storeService: StoreService
|
||||
customerService: CustomerService
|
||||
}
|
||||
|
||||
class SendProductsNewsletterSubscriber {
|
||||
protected sendGridService_: any
|
||||
protected productService_: ProductService
|
||||
protected storeService_: StoreService
|
||||
protected customerService_: CustomerService
|
||||
|
||||
constructor({
|
||||
eventBusService,
|
||||
sendgridService,
|
||||
productService,
|
||||
storeService,
|
||||
customerService,
|
||||
}: InjectedDependencies) {
|
||||
this.productService_ = productService
|
||||
this.sendGridService_ = sendgridService
|
||||
this.storeService_ = storeService
|
||||
this.customerService_ = customerService
|
||||
eventBusService.subscribe(
|
||||
"product.created",
|
||||
this.handleProductCreated
|
||||
)
|
||||
type InjectedDependencies = {
|
||||
eventBusService: EventBusService
|
||||
sendgridService: any
|
||||
productService: ProductService
|
||||
storeService: StoreService
|
||||
customerService: CustomerService
|
||||
}
|
||||
|
||||
handleProductCreated = async ({ id }) => {
|
||||
// retrieve store to have access to last send date
|
||||
const store = await this.storeService_.retrieve()
|
||||
|
||||
const productFilters: ProductSelector = {}
|
||||
if (store.metadata.last_send_date) {
|
||||
productFilters.created_at = {
|
||||
gt: new Date(store.metadata.last_send_date as string),
|
||||
class SendProductsNewsletterSubscriber {
|
||||
protected sendGridService_: any
|
||||
protected productService_: ProductService
|
||||
protected storeService_: StoreService
|
||||
protected customerService_: CustomerService
|
||||
|
||||
constructor({
|
||||
eventBusService,
|
||||
sendgridService,
|
||||
productService,
|
||||
storeService,
|
||||
customerService,
|
||||
}: InjectedDependencies) {
|
||||
this.productService_ = productService
|
||||
this.sendGridService_ = sendgridService
|
||||
this.storeService_ = storeService
|
||||
this.customerService_ = customerService
|
||||
eventBusService.subscribe(
|
||||
"product.created",
|
||||
this.handleProductCreated
|
||||
)
|
||||
}
|
||||
|
||||
handleProductCreated = async ({ id }) => {
|
||||
// retrieve store to have access to last send date
|
||||
const store = await this.storeService_.retrieve()
|
||||
|
||||
const productFilters: ProductSelector = {}
|
||||
if (store.metadata.last_send_date) {
|
||||
productFilters.created_at = {
|
||||
gt: new Date(store.metadata.last_send_date as string),
|
||||
}
|
||||
}
|
||||
|
||||
const products = await this.productService_.list(
|
||||
productFilters
|
||||
)
|
||||
|
||||
if (products.length > 10) {
|
||||
// get subscribed customers
|
||||
const customers = await this.customerService_.list({
|
||||
metadata: {
|
||||
is_subscribed: true,
|
||||
},
|
||||
})
|
||||
this.sendGridService_.sendEmail({
|
||||
templateId: "product-newsletter",
|
||||
from: "hello@medusajs.com",
|
||||
to: customers.map((customer) => ({
|
||||
name: customer.first_name,
|
||||
email: customer.email,
|
||||
})),
|
||||
dynamic_template_data: {
|
||||
// any data necessary for your template...
|
||||
products,
|
||||
},
|
||||
})
|
||||
|
||||
await this.storeService_.update({
|
||||
metadata: {
|
||||
last_send_date: new Date(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const products = await this.productService_.list(
|
||||
productFilters
|
||||
)
|
||||
|
||||
if (products.length > 10) {
|
||||
// get subscribed customers
|
||||
const customers = await this.customerService_.list({
|
||||
metadata: {
|
||||
is_subscribed: true,
|
||||
},
|
||||
})
|
||||
this.sendGridService_.sendEmail({
|
||||
templateId: "product-newsletter",
|
||||
from: "hello@medusajs.com",
|
||||
to: customers.map((customer) => ({
|
||||
name: customer.first_name,
|
||||
email: customer.email,
|
||||
})),
|
||||
dynamic_template_data: {
|
||||
// any data necessary for your template...
|
||||
products,
|
||||
},
|
||||
})
|
||||
|
||||
await this.storeService_.update({
|
||||
metadata: {
|
||||
last_send_date: new Date(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default SendProductsNewsletterSubscriber
|
||||
```
|
||||
export default SendProductsNewsletterSubscriber
|
||||
```
|
||||
|
||||
</details>
|
||||
</Details>
|
||||
|
||||
---
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -41,105 +41,105 @@ To associate these entities with the `Store` entity, you need to extend and cust
|
||||
}
|
||||
}} />
|
||||
|
||||
<details>
|
||||
<summary>Example: Associate User with Store</summary>
|
||||
<Details>
|
||||
<Summary>Example: Associate User with Store</Summary>
|
||||
|
||||
For example, to associate the `User` entity with the `Store` entity, create the file `src/models/user.ts` with the following content:
|
||||
For example, to associate the `User` entity with the `Store` entity, create the file `src/models/user.ts` with the following content:
|
||||
|
||||
```ts title=src/models/user.ts
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
} from "typeorm"
|
||||
import {
|
||||
User as MedusaUser,
|
||||
} from "@medusajs/medusa"
|
||||
import { Store } from "./store"
|
||||
```ts title=src/models/user.ts
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
Index,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
} from "typeorm"
|
||||
import {
|
||||
User as MedusaUser,
|
||||
} from "@medusajs/medusa"
|
||||
import { Store } from "./store"
|
||||
|
||||
@Entity()
|
||||
export class User extends MedusaUser {
|
||||
@Index("UserStoreId")
|
||||
@Column({ nullable: true })
|
||||
store_id?: string
|
||||
@Entity()
|
||||
export class User extends MedusaUser {
|
||||
@Index("UserStoreId")
|
||||
@Column({ nullable: true })
|
||||
store_id?: string
|
||||
|
||||
@ManyToOne(() => Store, (store) => store.members)
|
||||
@JoinColumn({ name: "store_id", referencedColumnName: "id" })
|
||||
store?: Store
|
||||
}
|
||||
```
|
||||
@ManyToOne(() => Store, (store) => store.members)
|
||||
@JoinColumn({ name: "store_id", referencedColumnName: "id" })
|
||||
store?: Store
|
||||
}
|
||||
```
|
||||
|
||||
Then, you need to extend the `UserRepository` to point to your extended entity. To do that, create the file `src/repositories/user.ts` with the following content:
|
||||
Then, you need to extend the `UserRepository` to point to your extended entity. To do that, create the file `src/repositories/user.ts` with the following content:
|
||||
|
||||
```ts title=src/repositories/user.ts
|
||||
import { User } from "../models/user"
|
||||
import {
|
||||
dataSource,
|
||||
} from "@medusajs/medusa/dist/loaders/database"
|
||||
import {
|
||||
UserRepository as MedusaUserRepository,
|
||||
} from "@medusajs/medusa/dist/repositories/user"
|
||||
```ts title=src/repositories/user.ts
|
||||
import { User } from "../models/user"
|
||||
import {
|
||||
dataSource,
|
||||
} from "@medusajs/medusa/dist/loaders/database"
|
||||
import {
|
||||
UserRepository as MedusaUserRepository,
|
||||
} from "@medusajs/medusa/dist/repositories/user"
|
||||
|
||||
export const UserRepository = dataSource
|
||||
.getRepository(User)
|
||||
.extend({
|
||||
...Object.assign(
|
||||
MedusaUserRepository,
|
||||
{ target: User }
|
||||
),
|
||||
})
|
||||
export const UserRepository = dataSource
|
||||
.getRepository(User)
|
||||
.extend({
|
||||
...Object.assign(
|
||||
MedusaUserRepository,
|
||||
{ target: User }
|
||||
),
|
||||
})
|
||||
|
||||
export default UserRepository
|
||||
```
|
||||
export default UserRepository
|
||||
```
|
||||
|
||||
Next, you need to create a migration that reflects the changes on the `User` entity in your database. To do that, run the following command to create a migration file:
|
||||
Next, you need to create a migration that reflects the changes on the `User` entity in your database. To do that, run the following command to create a migration file:
|
||||
|
||||
```bash
|
||||
npx typeorm migration:create src/migrations/add-user-store-id
|
||||
```
|
||||
```bash
|
||||
npx typeorm migration:create src/migrations/add-user-store-id
|
||||
```
|
||||
|
||||
This creates a file in the `src/migrations` directory of the format `<TIMESTAMP>_add-user-store-id.ts`. Replace the `up` and `down` methods in that file with the methods here:
|
||||
This creates a file in the `src/migrations` directory of the format `<TIMESTAMP>_add-user-store-id.ts`. Replace the `up` and `down` methods in that file with the methods here:
|
||||
|
||||
```ts title=src/migrations/<TIMESTAMP>_add-user-store-id.ts
|
||||
// ...
|
||||
```ts title=src/migrations/<TIMESTAMP>_add-user-store-id.ts
|
||||
// ...
|
||||
|
||||
export class AddUserStoreId1681287255173
|
||||
implements MigrationInterface {
|
||||
// ...
|
||||
export class AddUserStoreId1681287255173
|
||||
implements MigrationInterface {
|
||||
// ...
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user" ADD "store_id" character varying`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "UserStoreId" ON "user" ("store_id")`
|
||||
)
|
||||
}
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user" ADD "store_id" character varying`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "UserStoreId" ON "user" ("store_id")`
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."UserStoreId"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user" DROP COLUMN "store_id"`
|
||||
)
|
||||
}
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."UserStoreId"`
|
||||
)
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "user" DROP COLUMN "store_id"`
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
}
|
||||
```
|
||||
|
||||
Finally, to reflect these changes and start using them, `build` your changes and run migrations with the following commands:
|
||||
Finally, to reflect these changes and start using them, `build` your changes and run migrations with the following commands:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
npx medusa migrations run
|
||||
```
|
||||
```bash npm2yarn
|
||||
npm run build
|
||||
npx medusa migrations run
|
||||
```
|
||||
|
||||
You can extend other entities in a similar manner to associate them with a store.
|
||||
You can extend other entities in a similar manner to associate them with a store.
|
||||
|
||||
</details>
|
||||
</Details>
|
||||
|
||||
---
|
||||
|
||||
@@ -179,75 +179,76 @@ You can also extend services if you need to customize a functionality implemente
|
||||
}
|
||||
}} />
|
||||
|
||||
<details>
|
||||
<summary>Example: Extend User Service</summary>
|
||||
<Details>
|
||||
<Summary>Example: Extend User Service</Summary>
|
||||
|
||||
You can extend the user service to change how the `create` method is implemented.
|
||||
You can extend the user service to change how the `create` method is implemented.
|
||||
|
||||
To extend the user service, create the file `src/services/user.ts` with the following content:
|
||||
To extend the user service, create the file `src/services/user.ts` with the following content:
|
||||
|
||||
<!-- eslint-disable prefer-rest-params -->
|
||||
|
||||
```ts title=src/services/user.ts
|
||||
import { Lifetime } from "awilix"
|
||||
import {
|
||||
UserService as MedusaUserService,
|
||||
} from "@medusajs/medusa"
|
||||
import { User } from "../models/user"
|
||||
import {
|
||||
CreateUserInput as MedusaCreateUserInput,
|
||||
} from "@medusajs/medusa/dist/types/user"
|
||||
import StoreRepository from "../repositories/store"
|
||||
```ts title=src/services/user.ts
|
||||
import { Lifetime } from "awilix"
|
||||
import {
|
||||
UserService as MedusaUserService,
|
||||
} from "@medusajs/medusa"
|
||||
import { User } from "../models/user"
|
||||
import {
|
||||
CreateUserInput as MedusaCreateUserInput,
|
||||
} from "@medusajs/medusa/dist/types/user"
|
||||
import StoreRepository from "../repositories/store"
|
||||
|
||||
type CreateUserInput = {
|
||||
store_id?: string
|
||||
} & MedusaCreateUserInput
|
||||
type CreateUserInput = {
|
||||
store_id?: string
|
||||
} & MedusaCreateUserInput
|
||||
|
||||
class UserService extends MedusaUserService {
|
||||
static LIFE_TIME = Lifetime.SCOPED
|
||||
protected readonly loggedInUser_: User | null
|
||||
protected readonly storeRepository_: typeof StoreRepository
|
||||
class UserService extends MedusaUserService {
|
||||
static LIFE_TIME = Lifetime.SCOPED
|
||||
protected readonly loggedInUser_: User | null
|
||||
protected readonly storeRepository_: typeof StoreRepository
|
||||
|
||||
constructor(container, options) {
|
||||
super(...arguments)
|
||||
this.storeRepository_ = container.storeRepository
|
||||
constructor(container, options) {
|
||||
super(...arguments)
|
||||
this.storeRepository_ = container.storeRepository
|
||||
|
||||
try {
|
||||
this.loggedInUser_ = container.loggedInUser
|
||||
} catch (e) {
|
||||
// avoid errors when backend first runs
|
||||
try {
|
||||
this.loggedInUser_ = container.loggedInUser
|
||||
} catch (e) {
|
||||
// avoid errors when backend first runs
|
||||
}
|
||||
}
|
||||
|
||||
async create(
|
||||
user: CreateUserInput,
|
||||
password: string
|
||||
): Promise<User> {
|
||||
if (!user.store_id) {
|
||||
const storeRepo = this.manager_.withRepository(
|
||||
this.storeRepository_
|
||||
)
|
||||
let newStore = storeRepo.create()
|
||||
newStore = await storeRepo.save(newStore)
|
||||
user.store_id = newStore.id
|
||||
}
|
||||
|
||||
return await super.create(user, password)
|
||||
}
|
||||
}
|
||||
|
||||
async create(
|
||||
user: CreateUserInput,
|
||||
password: string
|
||||
): Promise<User> {
|
||||
if (!user.store_id) {
|
||||
const storeRepo = this.manager_.withRepository(
|
||||
this.storeRepository_
|
||||
)
|
||||
let newStore = storeRepo.create()
|
||||
newStore = await storeRepo.save(newStore)
|
||||
user.store_id = newStore.id
|
||||
}
|
||||
export default UserService
|
||||
```
|
||||
|
||||
return await super.create(user, password)
|
||||
}
|
||||
}
|
||||
In the `create` method of this extended service, you create a new store if the user being created doesn't have a store associated with it.
|
||||
|
||||
export default UserService
|
||||
```
|
||||
You can then test out your customization by running the `build` command and starting the backend:
|
||||
|
||||
In the `create` method of this extended service, you create a new store if the user being created doesn't have a store associated with it.
|
||||
```bash
|
||||
npm run build
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
You can then test out your customization by running the `build` command and starting the backend:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npx medusa develop
|
||||
```
|
||||
</details>
|
||||
</Details>
|
||||
|
||||
---
|
||||
|
||||
@@ -267,34 +268,35 @@ To listen to events, you need to create Subscribers that subscribe a handler met
|
||||
}
|
||||
}} />
|
||||
|
||||
<details>
|
||||
<summary>Example: Listen to Order Created Event</summary>
|
||||
<Details>
|
||||
<Summary>Example: Listen to Order Created Event</Summary>
|
||||
|
||||
To listen to the `order.placed` event, create the file `src/subscribers/orderNotifier.ts` with the following content:
|
||||
To listen to the `order.placed` event, create the file `src/subscribers/orderNotifier.ts` with the following content:
|
||||
|
||||
```ts title=src/subscribers/orderNotifier.ts
|
||||
class OrderNotifierSubscriber {
|
||||
constructor({ eventBusService }) {
|
||||
eventBusService.subscribe("order.placed", this.handleOrder)
|
||||
```ts title=src/subscribers/orderNotifier.ts
|
||||
class OrderNotifierSubscriber {
|
||||
constructor({ eventBusService }) {
|
||||
eventBusService.subscribe("order.placed", this.handleOrder)
|
||||
}
|
||||
|
||||
handleOrder = async (data) => {
|
||||
// TODO perform functionality
|
||||
}
|
||||
}
|
||||
|
||||
handleOrder = async (data) => {
|
||||
// TODO perform functionality
|
||||
}
|
||||
}
|
||||
export default OrderNotifierSubscriber
|
||||
```
|
||||
|
||||
export default OrderNotifierSubscriber
|
||||
```
|
||||
This subscribes the `handleOrder` method to be executed whenever the `order.placed` event is emitted.
|
||||
|
||||
This subscribes the `handleOrder` method to be executed whenever the `order.placed` event is emitted.
|
||||
You can then test out your subscriber by running the `build` command and starting the backend:
|
||||
|
||||
You can then test out your subscriber by running the `build` command and starting the backend:
|
||||
```bash
|
||||
npm run build
|
||||
npx medusa develop
|
||||
```
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
npx medusa develop
|
||||
```
|
||||
</details>
|
||||
</Details>
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -94,50 +94,48 @@ To search through product variants by their barcode, you can create a custom API
|
||||
},
|
||||
]} />
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
Example: Search Products By Barcode API Route
|
||||
</summary>
|
||||
<Details>
|
||||
<Summary>Example: Search Products By Barcode API Route</Summary>
|
||||
|
||||
Here’s an example of creating a custom API Route at `/store/pos/search-barcode` that searches product variants by a barcode:
|
||||
Here’s an example of creating a custom API Route at `/store/pos/search-barcode` that searches product variants by a barcode:
|
||||
|
||||
```ts title=src/api/store/pos/search-barcode/route.ts
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
ProductVariantService,
|
||||
} from "@medusajs/medusa"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
```ts title=src/api/store/pos/search-barcode/route.ts
|
||||
import type {
|
||||
MedusaRequest,
|
||||
MedusaResponse,
|
||||
ProductVariantService,
|
||||
} from "@medusajs/medusa"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const barcode = (req.query.barcode as string) || ""
|
||||
if (!barcode) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Barcode is required"
|
||||
)
|
||||
}
|
||||
// get product service
|
||||
const productVariantService = req.scope.resolve<
|
||||
ProductVariantService
|
||||
>("productVariantService")
|
||||
export const GET = async (
|
||||
req: MedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const barcode = (req.query.barcode as string) || ""
|
||||
if (!barcode) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Barcode is required"
|
||||
)
|
||||
}
|
||||
// get product service
|
||||
const productVariantService = req.scope.resolve<
|
||||
ProductVariantService
|
||||
>("productVariantService")
|
||||
|
||||
// retrieve product variants by barcode
|
||||
const productVariants = await productVariantService
|
||||
.list({
|
||||
barcode,
|
||||
// retrieve product variants by barcode
|
||||
const productVariants = await productVariantService
|
||||
.list({
|
||||
barcode,
|
||||
})
|
||||
|
||||
res.json({
|
||||
product_variants: productVariants,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
res.json({
|
||||
product_variants: productVariants,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
</Details>
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user