19 |
20 | > [!IMPORTANT]
21 | > **This repo has been archived and only concerns version 1.x of Medusa**.
22 | > **If you still want to apply it to your project, please follow the blog ["here"](https://blog.perseides.org/marketplace-lets-follow-the-recipe)**
23 |
24 |
25 | Initial repository for the perseides / marketplace series of blog posts.
26 |
58 |
59 | The last step is to create a sample order using the product you just created. You can then view your order’s details, process its payment, fulfillment, inventory, and more.
60 |
61 |
62 | By clicking the “Create a Sample Order” button, we’ll generate an order using the product you created and default configurations.
63 |
64 |
36 | On this page, you can view your product's details and edit them.
37 |
38 | You can preview your product using Medusa's Store APIs. You can copy any
39 | of the following code snippets to try it out.
40 |
41 |
46 |
47 | Create a product and set its general details such as title and
48 | description, its price, options, variants, images, and more. You'll then
49 | use the product to create a sample order.
50 |
51 |
52 | You can create a product by clicking the "New Product" button below.
53 | Alternatively, if you're not ready to create your own product, we can
54 | create a sample one for you.
55 |
56 | {!isComplete && (
57 |
58 |
66 |
67 | )}
68 |
69 | );
70 | };
71 |
72 | export default ProductsListDefault;
73 |
--------------------------------------------------------------------------------
/src/admin/components/onboarding-flow/nextjs/orders/order-detail.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { CurrencyDollarSolid, NextJs, ComputerDesktopSolid } from "@medusajs/icons";
3 | import { IconBadge, Heading, Text } from "@medusajs/ui";
4 |
5 | const OrderDetailNextjs = () => {
6 | const queryParams = `?ref=onboarding&type=${
7 | process.env.MEDUSA_ADMIN_ONBOARDING_TYPE || "nextjs"
8 | }`;
9 | return (
10 | <>
11 |
12 | You finished the setup guide 🎉. You now have a complete ecommerce store
13 | with a backend, admin, and a Next.js storefront. Feel free to play
14 | around with each of these components to experience all commerce features
15 | that Medusa provides.
16 |
17 |
21 | Continue Building your Ecommerce Store
22 |
23 |
24 | Your ecommerce store provides all basic ecommerce features you need to
25 | start selling. You can add more functionalities, add plugins for
26 | third-party integrations, and customize the storefront’s look and feel
27 | to support your use case.
28 |
29 |
48 |
49 | The last step is to create a sample order using one of your products. You can then view your order’s details, process its payment, fulfillment, inventory, and more.
50 |
51 |
52 | You can use the button below to experience hand-first the checkout flow in the Next.js storefront. After placing the order in the storefront, you’ll be directed back here to view the order’s details.
53 |
54 |
10 |
11 | We have now created a few sample products in your Medusa store. You can scroll down to see what the Product Detail view looks like in the Admin dashboard.
12 | This is also the view you use to edit existing products.
13 |
14 |
15 | To view the products in your store, you can visit the Next.js Storefront that was installed with create-medusa-app.
16 |
17 |
18 | The Next.js Storefront Starter is a template that helps you start building an ecommerce store with Medusa.
19 | You control the code for the storefront and you can customize it further to fit your specific needs.
20 |
21 |
22 | Click the button below to view the products in your Next.js Storefront.
23 |
24 |
25 | Having trouble? Click{" "}
26 |
31 | here
32 | .
33 |
34 |
61 |
62 | Products in Medusa represent the products you sell. You can set their general details including a
63 | title and description. Each product has options and variants, and you can set a price for each variant.
64 |
65 |
66 | Click the button below to create sample products.
67 |
68 | {!isComplete && (
69 |
425 |
426 | Thank you for completing the setup guide!
427 |
428 |
429 | This whole experience was built using our new{" "}
430 | widgets feature.
431 | You can find out more details and build your own by
432 | following{" "}
433 |
438 | our guide
439 |
440 | .
441 |
442 |
486 | }
487 |
488 |
489 | >
490 | );
491 | };
492 |
493 | export const config: WidgetConfig = {
494 | zone: [
495 | "product.list.before",
496 | "product.details.before",
497 | "order.list.before",
498 | "order.details.before",
499 | ],
500 | };
501 |
502 | export default OnboardingFlow;
503 |
--------------------------------------------------------------------------------
/src/api/README.md:
--------------------------------------------------------------------------------
1 | # Custom API Routes
2 |
3 | You may define custom API Routes by putting files in the `/api` directory that export functions returning an express router or a collection of express routers.
4 | Medusa supports adding custom API Routes using a file based approach. This means that you can add files in the `/api` directory and the files path will be used as the API Route path. For example, if you add a file called `/api/store/custom/route.ts` it will be available on the `/store/custom` API Route.
5 |
6 | ```ts
7 | import type { MedusaRequest, MedusaResponse } from "@medusajs/medusa";
8 |
9 | export async function GET(req: MedusaRequest, res: MedusaResponse) {
10 | res.json({
11 | message: "Hello world!",
12 | });
13 | }
14 | ```
15 |
16 | ## Supported HTTP methods
17 |
18 | The file based routing supports the following HTTP methods:
19 |
20 | - GET
21 | - POST
22 | - PUT
23 | - PATCH
24 | - DELETE
25 | - OPTIONS
26 | - HEAD
27 |
28 | You can define a handler for each of these methods by exporting a function with the name of the method in the paths `route.ts` file. For example, if you want to define a handler for the `GET`, `POST`, and `PUT` methods, you can do so by exporting functions with the names `GET`, `POST`, and `PUT`:
29 |
30 | ```ts
31 | import type { MedusaRequest, MedusaResponse } from "@medusajs/medusa";
32 |
33 | export async function GET(req: MedusaRequest, res: MedusaResponse) {
34 | // Handle GET requests
35 | }
36 |
37 | export async function POST(req: MedusaRequest, res: MedusaResponse) {
38 | // Handle POST requests
39 | }
40 |
41 | export async function PUT(req: MedusaRequest, res: MedusaResponse) {
42 | // Handle PUT requests
43 | }
44 | ```
45 |
46 | ## Parameters
47 |
48 | You can define parameters in the path of your route by using wrapping the parameter name in square brackets. For example, if you want to define a route that takes a `productId` parameter, you can do so by creating a file called `/api/products/[productId]/route.ts`:
49 |
50 | ```ts
51 | import type {
52 | MedusaRequest,
53 | MedusaResponse,
54 | ProductService,
55 | } from "@medusajs/medusa";
56 |
57 | export async function GET(req: MedusaRequest, res: MedusaResponse) {
58 | const { productId } = req.params;
59 |
60 | const productService: ProductService = req.scope.resolve("productService");
61 |
62 | const product = await productService.retrieve(productId);
63 |
64 | res.json({
65 | product,
66 | });
67 | }
68 | ```
69 |
70 | If you want to define a route that takes multiple parameters, you can do so by adding multiple parameters in the path. It is important that each parameter is given a unique name. For example, if you want to define a route that takes both a `productId` and a `variantId` parameter, you can do so by creating a file called `/api/products/[productId]/variants/[variantId]/route.ts`. Duplicate parameter names are not allowed, and will result in an error.
71 |
72 | ## Using the container
73 |
74 | A global container is available on `req.scope` to allow you to use any of the registered services from the core, installed plugins or your local project:
75 |
76 | ```ts
77 | import type {
78 | MedusaRequest,
79 | MedusaResponse,
80 | ProductService,
81 | } from "@medusajs/medusa";
82 |
83 | export async function GET(req: MedusaRequest, res: MedusaResponse) {
84 | const productService: ProductService = req.scope.resolve("productService");
85 |
86 | const products = await productService.list();
87 |
88 | res.json({
89 | products,
90 | });
91 | }
92 | ```
93 |
94 | ## Middleware
95 |
96 | You can apply middleware to your routes by creating a file called `/api/middlewares.ts`. This file should export a configuration object with what middleware you want to apply to which routes. For example, if you want to apply a custom middleware function to the `/store/custom` route, you can do so by adding the following to your `/api/middlewares.ts` file:
97 |
98 | ```ts
99 | import type {
100 | MiddlewaresConfig,
101 | MedusaRequest,
102 | MedusaResponse,
103 | MedusaNextFunction,
104 | } from "@medusajs/medusa";
105 |
106 | async function logger(
107 | req: MedusaRequest,
108 | res: MedusaResponse,
109 | next: MedusaNextFunction
110 | ) {
111 | console.log("Request received");
112 | next();
113 | }
114 |
115 | export const config: MiddlewaresConfig = {
116 | routes: [
117 | {
118 | matcher: "/store/custom",
119 | middlewares: [logger],
120 | },
121 | ],
122 | };
123 | ```
124 |
125 | The `matcher` property can be either a string or a regular expression. The `middlewares` property accepts an array of middleware functions.
126 |
127 | You might only want to apply middleware to certain HTTP methods. You can do so by adding a `method` property to the route configuration object:
128 |
129 | ```ts
130 | export const config: MiddlewaresConfig = {
131 | routes: [
132 | {
133 | matcher: "/store/custom",
134 | method: "GET",
135 | middlewares: [logger],
136 | },
137 | ],
138 | };
139 | ```
140 |
141 | The `method` property can be either a HTTP method or an array of HTTP methods. By default the middlewares will apply to all HTTP methods for the given `matcher`.
142 |
143 | ### Default middleware
144 |
145 | Some middleware functions are applied per default:
146 |
147 | #### Global middleware
148 |
149 | JSON parsing is applied to all routes. This means that you can access the request body as `req.body` and it will be parsed as JSON, if the request has a `Content-Type` header of `application/json`.
150 |
151 | If you want to use a different parser for a specific route, such as `urlencoded`, you can do so by adding the following export to your `route.ts` file:
152 |
153 | ```ts
154 | import { urlencoded } from "express";
155 |
156 | export const config: MiddlewaresConfig = {
157 | routes: [
158 | {
159 | method: "POST",
160 | matcher: "/store/custom",
161 | middlewares: [urlencoded()],
162 | },
163 | ],
164 | };
165 | ```
166 |
167 | #### Store middleware
168 |
169 | For all `/store` routes, the appropriate CORS settings are applied. The STORE_CORS value can be configured in your `medusa-config.js` file.
170 |
171 | #### Admin middleware
172 |
173 | For all `/admin` routes, the appropriate CORS settings are applied. The ADMIN_CORS value can be configured in your `medusa-config.js` file.
174 |
175 | All `/admin` routes also have admin authentication applied per default. If you want to disable this for a specific route, you can do so by adding the following export to your `route.ts` file:
176 |
177 | ```ts
178 | export const AUTHENTICATE = false;
179 | ```
180 |
--------------------------------------------------------------------------------
/src/api/admin/custom/route.ts:
--------------------------------------------------------------------------------
1 | import { MedusaRequest, MedusaResponse } from "@medusajs/medusa";
2 |
3 | export async function GET(
4 | req: MedusaRequest,
5 | res: MedusaResponse
6 | ): Promise {
7 | res.sendStatus(200);
8 | }
9 |
--------------------------------------------------------------------------------
/src/api/admin/onboarding/route.ts:
--------------------------------------------------------------------------------
1 | import type { MedusaRequest, MedusaResponse } from "@medusajs/medusa";
2 | import { EntityManager } from "typeorm";
3 |
4 | import OnboardingService from "../../../services/onboarding";
5 |
6 | export async function GET(req: MedusaRequest, res: MedusaResponse) {
7 | const onboardingService: OnboardingService =
8 | req.scope.resolve("onboardingService");
9 |
10 | const status = await onboardingService.retrieve();
11 |
12 | res.status(200).json({ status });
13 | }
14 |
15 | export async function POST(req: MedusaRequest, res: MedusaResponse) {
16 | const onboardingService: OnboardingService =
17 | req.scope.resolve("onboardingService");
18 | const manager: EntityManager = req.scope.resolve("manager");
19 |
20 | const status = await manager.transaction(async (transactionManager) => {
21 | return await onboardingService
22 | .withTransaction(transactionManager)
23 | .update(req.body);
24 | });
25 |
26 | res.status(200).json({ status });
27 | }
28 |
--------------------------------------------------------------------------------
/src/api/store/custom/route.ts:
--------------------------------------------------------------------------------
1 | import { MedusaRequest, MedusaResponse } from "@medusajs/medusa";
2 |
3 | export async function GET(
4 | req: MedusaRequest,
5 | res: MedusaResponse
6 | ): Promise {
7 | res.sendStatus(200);
8 | }
9 |
--------------------------------------------------------------------------------
/src/jobs/README.md:
--------------------------------------------------------------------------------
1 | # Custom scheduled jobs
2 |
3 | You may define custom scheduled jobs (cron jobs) by creating files in the `/jobs` directory.
4 |
5 | ```ts
6 | import {
7 | ProductService,
8 | ScheduledJobArgs,
9 | ScheduledJobConfig,
10 | } from "@medusajs/medusa";
11 |
12 | export default async function myCustomJob({ container }: ScheduledJobArgs) {
13 | const productService: ProductService = container.resolve("productService");
14 |
15 | const products = await productService.listAndCount();
16 |
17 | // Do something with the products
18 | }
19 |
20 | export const config: ScheduledJobConfig = {
21 | name: "daily-product-report",
22 | schedule: "0 0 * * *", // Every day at midnight
23 | };
24 | ```
25 |
26 | A scheduled job is defined in two parts a `handler` and a `config`. The `handler` is a function which is invoked when the job is scheduled. The `config` is an object which defines the name of the job, the schedule, and an optional data object.
27 |
28 | The `handler` is a function which takes one parameter, an `object` of type `ScheduledJobArgs` with the following properties:
29 |
30 | - `container` - a `MedusaContainer` instance which can be used to resolve services.
31 | - `data` - an `object` containing data passed to the job when it was scheduled. This object is passed in the `config` object.
32 | - `pluginOptions` - an `object` containing plugin options, if the job is defined in a plugin.
33 |
--------------------------------------------------------------------------------
/src/loaders/README.md:
--------------------------------------------------------------------------------
1 | # Custom loader
2 |
3 | The loader allows you have access to the Medusa service container. This allows you to access the database and the services registered on the container.
4 | you can register custom registrations in the container or run custom code on startup.
5 |
6 | ```ts
7 | // src/loaders/my-loader.ts
8 |
9 | import { AwilixContainer } from 'awilix'
10 |
11 | /**
12 | *
13 | * @param container The container in which the registrations are made
14 | * @param config The options of the plugin or the entire config object
15 | */
16 | export default (container: AwilixContainer, config: Record): void | Promise => {
17 | /* Implement your own loader. */
18 | }
19 | ```
--------------------------------------------------------------------------------
/src/migrations/1685715079776-CreateOnboarding.ts:
--------------------------------------------------------------------------------
1 | import { generateEntityId } from "@medusajs/utils";
2 | import { MigrationInterface, QueryRunner } from "typeorm";
3 |
4 | export class CreateOnboarding1685715079776 implements MigrationInterface {
5 | public async up(queryRunner: QueryRunner): Promise {
6 | await queryRunner.query(
7 | `CREATE TABLE "onboarding_state" ("id" character varying NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "current_step" character varying NULL, "is_complete" boolean)`
8 | );
9 |
10 | await queryRunner.query(
11 | `INSERT INTO "onboarding_state" ("id", "current_step", "is_complete") VALUES ('${generateEntityId(
12 | "",
13 | "onboarding"
14 | )}' , NULL, false)`
15 | );
16 | }
17 |
18 | public async down(queryRunner: QueryRunner): Promise {
19 | await queryRunner.query(`DROP TABLE "onboarding_state"`);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/migrations/1686062614694-AddOnboardingProduct.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class AddOnboardingProduct1686062614694 implements MigrationInterface {
4 | public async up(queryRunner: QueryRunner): Promise {
5 | await queryRunner.query(
6 | `ALTER TABLE "onboarding_state" ADD COLUMN "product_id" character varying NULL`
7 | );
8 | }
9 |
10 | public async down(queryRunner: QueryRunner): Promise {
11 | await queryRunner.query(
12 | `ALTER TABLE "onboarding_state" DROP COLUMN "product_id"`
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/migrations/1690996567455-CorrectOnboardingFields.ts:
--------------------------------------------------------------------------------
1 | import { MigrationInterface, QueryRunner } from "typeorm";
2 |
3 | export class CorrectOnboardingFields1690996567455 implements MigrationInterface {
4 | name = 'CorrectOnboardingFields1690996567455'
5 |
6 | public async up(queryRunner: QueryRunner): Promise {
7 | await queryRunner.query(`ALTER TABLE "onboarding_state" ADD CONSTRAINT "PK_891b72628471aada55d7b8c9410" PRIMARY KEY ("id")`);
8 | await queryRunner.query(`ALTER TABLE "onboarding_state" ALTER COLUMN "is_complete" SET NOT NULL`);
9 | }
10 |
11 | public async down(queryRunner: QueryRunner): Promise {
12 | await queryRunner.query(`ALTER TABLE "onboarding_state" ALTER COLUMN "is_complete" DROP NOT NULL`);
13 | await queryRunner.query(`ALTER TABLE "onboarding_state" DROP CONSTRAINT "PK_891b72628471aada55d7b8c9410"`);
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/migrations/README.md:
--------------------------------------------------------------------------------
1 | # Custom migrations
2 |
3 | You may define custom models (entities) that will be registered on the global container by creating files in the `src/models` directory that export an instance of `BaseEntity`.
4 | In that case you also need to provide a migration in order to create the table in the database.
5 |
6 | ## Example
7 |
8 | ### 1. Create the migration
9 |
10 | See [How to Create Migrations](https://docs.medusajs.com/advanced/backend/migrations/) in the documentation.
11 |
12 | ```ts
13 | // src/migration/my-migration.ts
14 |
15 | import { MigrationInterface, QueryRunner } from "typeorm"
16 |
17 | export class MyMigration1617703530229 implements MigrationInterface {
18 | name = "myMigration1617703530229"
19 |
20 | public async up(queryRunner: QueryRunner): Promise {
21 | // write you migration here
22 | }
23 |
24 | public async down(queryRunner: QueryRunner): Promise {
25 | // write you migration here
26 | }
27 | }
28 |
29 | ```
--------------------------------------------------------------------------------
/src/models/README.md:
--------------------------------------------------------------------------------
1 | # Custom models
2 |
3 | You may define custom models (entities) that will be registered on the global container by creating files in the `src/models` directory that export an instance of `BaseEntity`.
4 |
5 | ## Example
6 |
7 | ### 1. Create the Entity
8 |
9 | ```ts
10 | // src/models/post.ts
11 |
12 | import { BeforeInsert, Column, Entity, PrimaryColumn } from "typeorm";
13 | import { generateEntityId } from "@medusajs/utils";
14 | import { BaseEntity } from "@medusajs/medusa";
15 |
16 | @Entity()
17 | export class Post extends BaseEntity {
18 | @Column({type: 'varchar'})
19 | title: string | null;
20 |
21 | @BeforeInsert()
22 | private beforeInsert(): void {
23 | this.id = generateEntityId(this.id, "post")
24 | }
25 | }
26 | ```
27 |
28 | ### 2. Create the Migration
29 |
30 | You also need to create a Migration to create the new table in the database. See [How to Create Migrations](https://docs.medusajs.com/advanced/backend/migrations/) in the documentation.
31 |
32 | ### 3. Create a Repository
33 | Entities data can be easily accessed and modified using [TypeORM Repositories](https://typeorm.io/working-with-repository). To create a repository, create a file in `src/repositories`. For example, here’s a repository `PostRepository` for the `Post` entity:
34 |
35 | ```ts
36 | // src/repositories/post.ts
37 |
38 | import { EntityRepository, Repository } from "typeorm"
39 |
40 | import { Post } from "../models/post"
41 |
42 | @EntityRepository(Post)
43 | export class PostRepository extends Repository { }
44 | ```
45 |
46 | See more about defining and accesing your custom [Entities](https://docs.medusajs.com/advanced/backend/entities/overview) in the documentation.
--------------------------------------------------------------------------------
/src/models/onboarding.ts:
--------------------------------------------------------------------------------
1 | import { BaseEntity } from "@medusajs/medusa";
2 | import { Column, Entity } from "typeorm";
3 |
4 | @Entity()
5 | export class OnboardingState extends BaseEntity {
6 | @Column({ nullable: true })
7 | current_step: string;
8 |
9 | @Column()
10 | is_complete: boolean;
11 |
12 | @Column({ nullable: true })
13 | product_id: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/repositories/onboarding.ts:
--------------------------------------------------------------------------------
1 | import { dataSource } from "@medusajs/medusa/dist/loaders/database";
2 | import { OnboardingState } from "../models/onboarding";
3 |
4 | const OnboardingRepository = dataSource.getRepository(OnboardingState);
5 |
6 | export default OnboardingRepository;
7 |
--------------------------------------------------------------------------------
/src/services/README.md:
--------------------------------------------------------------------------------
1 | # Custom services
2 |
3 | You may define custom services that will be registered on the global container by creating files in the `/services` directory that export an instance of `BaseService`.
4 |
5 | ```ts
6 | // src/services/my-custom.ts
7 |
8 | import { Lifetime } from "awilix"
9 | import { TransactionBaseService } from "@medusajs/medusa";
10 | import { IEventBusService } from "@medusajs/types";
11 |
12 | export default class MyCustomService extends TransactionBaseService {
13 | static LIFE_TIME = Lifetime.SCOPED
14 | protected readonly eventBusService_: IEventBusService
15 |
16 | constructor(
17 | { eventBusService }: { eventBusService: IEventBusService },
18 | options: Record
19 | ) {
20 | // @ts-ignore
21 | super(...arguments)
22 |
23 | this.eventBusService_ = eventBusService
24 | }
25 | }
26 |
27 | ```
28 |
29 | The first argument to the `constructor` is the global giving you access to easy dependency injection. The container holds all registered services from the core, installed plugins and from other files in the `/services` directory. The registration name is a camelCased version of the file name with the type appended i.e.: `my-custom.js` is registered as `myCustomService`, `custom-thing.js` is registered as `customThingService`.
30 |
31 | You may use the services you define here in custom endpoints by resolving the services defined.
32 |
33 | ```js
34 | import { Router } from "express"
35 |
36 | export default () => {
37 | const router = Router()
38 |
39 | router.get("/hello-product", async (req, res) => {
40 | const myService = req.scope.resolve("myCustomService")
41 |
42 | res.json({
43 | message: await myService.getProductMessage()
44 | })
45 | })
46 |
47 | return router;
48 | }
49 | ```
50 |
--------------------------------------------------------------------------------
/src/services/__tests__/test-service.spec.ts:
--------------------------------------------------------------------------------
1 | describe('MyService', () => {
2 | it('should do this', async () => {
3 | expect(true).toBe(true)
4 | })
5 | })
6 |
--------------------------------------------------------------------------------
/src/services/onboarding.ts:
--------------------------------------------------------------------------------
1 | import { TransactionBaseService } from "@medusajs/medusa";
2 | import OnboardingRepository from "../repositories/onboarding";
3 | import { OnboardingState } from "../models/onboarding";
4 | import { EntityManager, IsNull, Not } from "typeorm";
5 | import { UpdateOnboardingStateInput } from "../types/onboarding";
6 |
7 | type InjectedDependencies = {
8 | manager: EntityManager;
9 | onboardingRepository: typeof OnboardingRepository;
10 | };
11 |
12 | class OnboardingService extends TransactionBaseService {
13 | protected onboardingRepository_: typeof OnboardingRepository;
14 |
15 | constructor({ onboardingRepository }: InjectedDependencies) {
16 | super(arguments[0]);
17 |
18 | this.onboardingRepository_ = onboardingRepository;
19 | }
20 |
21 | async retrieve(): Promise {
22 | const onboardingRepo = this.activeManager_.withRepository(
23 | this.onboardingRepository_
24 | );
25 |
26 | const status = await onboardingRepo.findOne({
27 | where: { id: Not(IsNull()) },
28 | });
29 |
30 | return status;
31 | }
32 |
33 | async update(data: UpdateOnboardingStateInput): Promise {
34 | return await this.atomicPhase_(
35 | async (transactionManager: EntityManager) => {
36 | const onboardingRepository = transactionManager.withRepository(
37 | this.onboardingRepository_
38 | );
39 |
40 | const status = await this.retrieve();
41 |
42 | for (const [key, value] of Object.entries(data)) {
43 | status[key] = value;
44 | }
45 |
46 | return await onboardingRepository.save(status);
47 | }
48 | );
49 | }
50 | }
51 |
52 | export default OnboardingService;
53 |
--------------------------------------------------------------------------------
/src/subscribers/README.md:
--------------------------------------------------------------------------------
1 | # Custom subscribers
2 |
3 | You may define custom eventhandlers, `subscribers` by creating files in the `/subscribers` directory.
4 |
5 | ```ts
6 | import MyCustomService from "../services/my-custom";
7 | import {
8 | OrderService,
9 | SubscriberArgs,
10 | SubscriberConfig,
11 | } from "@medusajs/medusa";
12 |
13 | type OrderPlacedEvent = {
14 | id: string;
15 | no_notification: boolean;
16 | };
17 |
18 | export default async function orderPlacedHandler({
19 | data,
20 | eventName,
21 | container,
22 | }: SubscriberArgs) {
23 | const orderService: OrderService = container.resolve(OrderService);
24 |
25 | const order = await orderService.retrieve(data.id, {
26 | relations: ["items", "items.variant", "items.variant.product"],
27 | });
28 |
29 | // Do something with the order
30 | }
31 |
32 | export const config: SubscriberConfig = {
33 | event: OrderService.Events.PLACED,
34 | };
35 | ```
36 |
37 | A subscriber is defined in two parts a `handler` and a `config`. The `handler` is a function which is invoked when an event is emitted. The `config` is an object which defines which event(s) the subscriber should subscribe to.
38 |
39 | The `handler` is a function which takes one parameter, an `object` of type `SubscriberArgs` with the following properties:
40 |
41 | - `data` - an `object` of type `T` containing information about the event.
42 | - `eventName` - a `string` containing the name of the event.
43 | - `container` - a `MedusaContainer` instance which can be used to resolve services.
44 | - `pluginOptions` - an `object` containing plugin options, if the subscriber is defined in a plugin.
45 |
--------------------------------------------------------------------------------
/src/types/onboarding.ts:
--------------------------------------------------------------------------------
1 | import { OnboardingState } from "../models/onboarding";
2 |
3 | export type UpdateOnboardingStateInput = {
4 | current_step?: string;
5 | is_complete?: boolean;
6 | product_id?: string;
7 | };
8 |
9 | export interface AdminOnboardingUpdateStateReq {}
10 |
11 | export type OnboardingStateRes = {
12 | status: OnboardingState;
13 | };
14 |
--------------------------------------------------------------------------------
/tsconfig.admin.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "esnext"
5 | },
6 | "include": ["src/admin"],
7 | "exclude": ["**/*.spec.js"]
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2019",
4 | "allowJs": true,
5 | "esModuleInterop": true,
6 | "module": "commonjs",
7 | "moduleResolution": "node",
8 | "emitDecoratorMetadata": true,
9 | "experimentalDecorators": true,
10 | "skipLibCheck": true,
11 | "skipDefaultLibCheck": true,
12 | "declaration": true,
13 | "sourceMap": false,
14 | "outDir": "./dist",
15 | "rootDir": "./src",
16 | "baseUrl": ".",
17 | "jsx": "react-jsx",
18 | "forceConsistentCasingInFileNames": true,
19 | "resolveJsonModule": true,
20 | "checkJs": false
21 | },
22 | "include": ["src/"],
23 | "exclude": [
24 | "**/__tests__",
25 | "**/__fixtures__",
26 | "node_modules",
27 | "build",
28 | ".cache"
29 | ]
30 | }
31 |
--------------------------------------------------------------------------------
/tsconfig.server.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | /* Emit a single file with source maps instead of having a separate file. */
5 | "inlineSourceMap": true
6 | },
7 | "exclude": ["src/admin", "**/*.spec.js"]
8 | }
9 |
--------------------------------------------------------------------------------
/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "include": ["src"],
4 | "exclude": ["dist", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------