@lodashventure/medusa-campaign 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.medusa/server/src/admin/index.js +1 -67
- package/.medusa/server/src/admin/index.mjs +1 -67
- package/.medusa/server/src/workflows/index.js +10 -0
- package/package.json +4 -4
- package/src/admin/README.md +31 -0
- package/src/admin/components/FlashSaleForm.tsx +379 -0
- package/src/admin/components/FlashSalePage.tsx +113 -0
- package/src/admin/components/ProductSelector.tsx +88 -0
- package/src/admin/hooks/useFlashSaleById.ts +21 -0
- package/src/admin/hooks/useFlashSales.ts +25 -0
- package/src/admin/lib/sdk.ts +10 -0
- package/src/admin/routes/flash-sales/[id]/page.tsx +105 -0
- package/src/admin/routes/flash-sales/create/page.tsx +51 -0
- package/src/admin/routes/flash-sales/page.tsx +15 -0
- package/src/admin/tsconfig.json +24 -0
- package/src/admin/types/campaign.ts +25 -0
- package/src/admin/vite-env.d.ts +1 -0
- package/src/api/README.md +133 -0
- package/src/api/admin/flash-sales/[id]/route.ts +164 -0
- package/src/api/admin/flash-sales/route.ts +87 -0
- package/src/api/middlewares.ts +32 -0
- package/src/api/store/campaigns/[id]/route.ts +133 -0
- package/src/api/store/campaigns/route.ts +36 -0
- package/src/jobs/README.md +36 -0
- package/src/links/README.md +26 -0
- package/src/links/campaign-type.ts +8 -0
- package/src/modules/README.md +116 -0
- package/src/modules/custom-campaigns/index.ts +8 -0
- package/src/modules/custom-campaigns/migrations/.snapshot-medusa-custom-campaign.json +235 -0
- package/src/modules/custom-campaigns/migrations/Migration20250524150901.ts +23 -0
- package/src/modules/custom-campaigns/migrations/Migration20250526010310.ts +20 -0
- package/src/modules/custom-campaigns/migrations/Migration20250529011904.ts +13 -0
- package/src/modules/custom-campaigns/models/custom-campaign-type.ts +10 -0
- package/src/modules/custom-campaigns/models/promotion-usage-limit.ts +14 -0
- package/src/modules/custom-campaigns/service.ts +10 -0
- package/src/modules/custom-campaigns/types/campaign-type.enum.ts +3 -0
- package/src/providers/README.md +30 -0
- package/src/subscribers/README.md +59 -0
- package/src/subscribers/order-placed.ts +17 -0
- package/src/workflows/README.md +79 -0
- package/src/workflows/custom-campaign/createCustomCampaignWorkflow.ts +181 -0
- package/src/workflows/custom-campaign/updateCustomFlashSaleWorkflow.ts +185 -0
- package/src/workflows/custom-campaign/updatePromotionUsageWorkflow.ts +70 -0
- package/src/workflows/hooks/deletePromotionOnCampaignDelete.ts +49 -0
- package/src/workflows/index.ts +3 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
{
|
|
2
|
+
"namespaces": [
|
|
3
|
+
"public"
|
|
4
|
+
],
|
|
5
|
+
"name": "public",
|
|
6
|
+
"tables": [
|
|
7
|
+
{
|
|
8
|
+
"columns": {
|
|
9
|
+
"id": {
|
|
10
|
+
"name": "id",
|
|
11
|
+
"type": "text",
|
|
12
|
+
"unsigned": false,
|
|
13
|
+
"autoincrement": false,
|
|
14
|
+
"primary": false,
|
|
15
|
+
"nullable": false,
|
|
16
|
+
"mappedType": "text"
|
|
17
|
+
},
|
|
18
|
+
"campaign_id": {
|
|
19
|
+
"name": "campaign_id",
|
|
20
|
+
"type": "text",
|
|
21
|
+
"unsigned": false,
|
|
22
|
+
"autoincrement": false,
|
|
23
|
+
"primary": false,
|
|
24
|
+
"nullable": false,
|
|
25
|
+
"mappedType": "text"
|
|
26
|
+
},
|
|
27
|
+
"type": {
|
|
28
|
+
"name": "type",
|
|
29
|
+
"type": "text",
|
|
30
|
+
"unsigned": false,
|
|
31
|
+
"autoincrement": false,
|
|
32
|
+
"primary": false,
|
|
33
|
+
"nullable": false,
|
|
34
|
+
"enumItems": [
|
|
35
|
+
"flash-sale"
|
|
36
|
+
],
|
|
37
|
+
"mappedType": "enum"
|
|
38
|
+
},
|
|
39
|
+
"created_at": {
|
|
40
|
+
"name": "created_at",
|
|
41
|
+
"type": "timestamptz",
|
|
42
|
+
"unsigned": false,
|
|
43
|
+
"autoincrement": false,
|
|
44
|
+
"primary": false,
|
|
45
|
+
"nullable": false,
|
|
46
|
+
"length": 6,
|
|
47
|
+
"default": "now()",
|
|
48
|
+
"mappedType": "datetime"
|
|
49
|
+
},
|
|
50
|
+
"updated_at": {
|
|
51
|
+
"name": "updated_at",
|
|
52
|
+
"type": "timestamptz",
|
|
53
|
+
"unsigned": false,
|
|
54
|
+
"autoincrement": false,
|
|
55
|
+
"primary": false,
|
|
56
|
+
"nullable": false,
|
|
57
|
+
"length": 6,
|
|
58
|
+
"default": "now()",
|
|
59
|
+
"mappedType": "datetime"
|
|
60
|
+
},
|
|
61
|
+
"deleted_at": {
|
|
62
|
+
"name": "deleted_at",
|
|
63
|
+
"type": "timestamptz",
|
|
64
|
+
"unsigned": false,
|
|
65
|
+
"autoincrement": false,
|
|
66
|
+
"primary": false,
|
|
67
|
+
"nullable": true,
|
|
68
|
+
"length": 6,
|
|
69
|
+
"mappedType": "datetime"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"name": "custom_campaign_type",
|
|
73
|
+
"schema": "public",
|
|
74
|
+
"indexes": [
|
|
75
|
+
{
|
|
76
|
+
"keyName": "IDX_custom_campaign_type_campaign_id_unique",
|
|
77
|
+
"columnNames": [],
|
|
78
|
+
"composite": false,
|
|
79
|
+
"constraint": false,
|
|
80
|
+
"primary": false,
|
|
81
|
+
"unique": false,
|
|
82
|
+
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_custom_campaign_type_campaign_id_unique\" ON \"custom_campaign_type\" (campaign_id) WHERE deleted_at IS NULL"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"keyName": "IDX_custom_campaign_type_deleted_at",
|
|
86
|
+
"columnNames": [],
|
|
87
|
+
"composite": false,
|
|
88
|
+
"constraint": false,
|
|
89
|
+
"primary": false,
|
|
90
|
+
"unique": false,
|
|
91
|
+
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_custom_campaign_type_deleted_at\" ON \"custom_campaign_type\" (deleted_at) WHERE deleted_at IS NULL"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"keyName": "custom_campaign_type_pkey",
|
|
95
|
+
"columnNames": [
|
|
96
|
+
"id"
|
|
97
|
+
],
|
|
98
|
+
"composite": false,
|
|
99
|
+
"constraint": true,
|
|
100
|
+
"primary": true,
|
|
101
|
+
"unique": true
|
|
102
|
+
}
|
|
103
|
+
],
|
|
104
|
+
"checks": [],
|
|
105
|
+
"foreignKeys": {},
|
|
106
|
+
"nativeEnums": {}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"columns": {
|
|
110
|
+
"id": {
|
|
111
|
+
"name": "id",
|
|
112
|
+
"type": "text",
|
|
113
|
+
"unsigned": false,
|
|
114
|
+
"autoincrement": false,
|
|
115
|
+
"primary": false,
|
|
116
|
+
"nullable": false,
|
|
117
|
+
"mappedType": "text"
|
|
118
|
+
},
|
|
119
|
+
"campaign_id": {
|
|
120
|
+
"name": "campaign_id",
|
|
121
|
+
"type": "text",
|
|
122
|
+
"unsigned": false,
|
|
123
|
+
"autoincrement": false,
|
|
124
|
+
"primary": false,
|
|
125
|
+
"nullable": false,
|
|
126
|
+
"mappedType": "text"
|
|
127
|
+
},
|
|
128
|
+
"promotion_id": {
|
|
129
|
+
"name": "promotion_id",
|
|
130
|
+
"type": "text",
|
|
131
|
+
"unsigned": false,
|
|
132
|
+
"autoincrement": false,
|
|
133
|
+
"primary": false,
|
|
134
|
+
"nullable": false,
|
|
135
|
+
"mappedType": "text"
|
|
136
|
+
},
|
|
137
|
+
"product_id": {
|
|
138
|
+
"name": "product_id",
|
|
139
|
+
"type": "text",
|
|
140
|
+
"unsigned": false,
|
|
141
|
+
"autoincrement": false,
|
|
142
|
+
"primary": false,
|
|
143
|
+
"nullable": false,
|
|
144
|
+
"mappedType": "text"
|
|
145
|
+
},
|
|
146
|
+
"limit": {
|
|
147
|
+
"name": "limit",
|
|
148
|
+
"type": "integer",
|
|
149
|
+
"unsigned": false,
|
|
150
|
+
"autoincrement": false,
|
|
151
|
+
"primary": false,
|
|
152
|
+
"nullable": false,
|
|
153
|
+
"mappedType": "integer"
|
|
154
|
+
},
|
|
155
|
+
"used": {
|
|
156
|
+
"name": "used",
|
|
157
|
+
"type": "integer",
|
|
158
|
+
"unsigned": false,
|
|
159
|
+
"autoincrement": false,
|
|
160
|
+
"primary": false,
|
|
161
|
+
"nullable": false,
|
|
162
|
+
"mappedType": "integer"
|
|
163
|
+
},
|
|
164
|
+
"created_at": {
|
|
165
|
+
"name": "created_at",
|
|
166
|
+
"type": "timestamptz",
|
|
167
|
+
"unsigned": false,
|
|
168
|
+
"autoincrement": false,
|
|
169
|
+
"primary": false,
|
|
170
|
+
"nullable": false,
|
|
171
|
+
"length": 6,
|
|
172
|
+
"default": "now()",
|
|
173
|
+
"mappedType": "datetime"
|
|
174
|
+
},
|
|
175
|
+
"updated_at": {
|
|
176
|
+
"name": "updated_at",
|
|
177
|
+
"type": "timestamptz",
|
|
178
|
+
"unsigned": false,
|
|
179
|
+
"autoincrement": false,
|
|
180
|
+
"primary": false,
|
|
181
|
+
"nullable": false,
|
|
182
|
+
"length": 6,
|
|
183
|
+
"default": "now()",
|
|
184
|
+
"mappedType": "datetime"
|
|
185
|
+
},
|
|
186
|
+
"deleted_at": {
|
|
187
|
+
"name": "deleted_at",
|
|
188
|
+
"type": "timestamptz",
|
|
189
|
+
"unsigned": false,
|
|
190
|
+
"autoincrement": false,
|
|
191
|
+
"primary": false,
|
|
192
|
+
"nullable": true,
|
|
193
|
+
"length": 6,
|
|
194
|
+
"mappedType": "datetime"
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"name": "promotion_usage_limit",
|
|
198
|
+
"schema": "public",
|
|
199
|
+
"indexes": [
|
|
200
|
+
{
|
|
201
|
+
"keyName": "IDX_promotion_usage_limit_deleted_at",
|
|
202
|
+
"columnNames": [],
|
|
203
|
+
"composite": false,
|
|
204
|
+
"constraint": false,
|
|
205
|
+
"primary": false,
|
|
206
|
+
"unique": false,
|
|
207
|
+
"expression": "CREATE INDEX IF NOT EXISTS \"IDX_promotion_usage_limit_deleted_at\" ON \"promotion_usage_limit\" (deleted_at) WHERE deleted_at IS NULL"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
"keyName": "IDX_promotion_usage_limit_campaign_id_promotion_id_unique",
|
|
211
|
+
"columnNames": [],
|
|
212
|
+
"composite": false,
|
|
213
|
+
"constraint": false,
|
|
214
|
+
"primary": false,
|
|
215
|
+
"unique": false,
|
|
216
|
+
"expression": "CREATE UNIQUE INDEX IF NOT EXISTS \"IDX_promotion_usage_limit_campaign_id_promotion_id_unique\" ON \"promotion_usage_limit\" (campaign_id, promotion_id) WHERE deleted_at IS NULL"
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
"keyName": "promotion_usage_limit_pkey",
|
|
220
|
+
"columnNames": [
|
|
221
|
+
"id"
|
|
222
|
+
],
|
|
223
|
+
"composite": false,
|
|
224
|
+
"constraint": true,
|
|
225
|
+
"primary": true,
|
|
226
|
+
"unique": true
|
|
227
|
+
}
|
|
228
|
+
],
|
|
229
|
+
"checks": [],
|
|
230
|
+
"foreignKeys": {},
|
|
231
|
+
"nativeEnums": {}
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
"nativeEnums": {}
|
|
235
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20250524150901 extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`alter table if exists "promotion_usage_limit" drop constraint if exists "promotion_usage_limit_promotion_id_unique";`);
|
|
7
|
+
this.addSql(`alter table if exists "custom_campaign_type" drop constraint if exists "custom_campaign_type_campaign_id_unique";`);
|
|
8
|
+
this.addSql(`create table if not exists "custom_campaign_type" ("id" text not null, "campaign_id" text not null, "type" text check ("type" in ('flash-sale')) not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "custom_campaign_type_pkey" primary key ("id"));`);
|
|
9
|
+
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_custom_campaign_type_campaign_id_unique" ON "custom_campaign_type" (campaign_id) WHERE deleted_at IS NULL;`);
|
|
10
|
+
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_custom_campaign_type_deleted_at" ON "custom_campaign_type" (deleted_at) WHERE deleted_at IS NULL;`);
|
|
11
|
+
|
|
12
|
+
this.addSql(`create table if not exists "promotion_usage_limit" ("id" text not null, "promotion_id" text not null, "limit" integer not null, "used" integer not null, "created_at" timestamptz not null default now(), "updated_at" timestamptz not null default now(), "deleted_at" timestamptz null, constraint "promotion_usage_limit_pkey" primary key ("id"));`);
|
|
13
|
+
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_promotion_usage_limit_promotion_id_unique" ON "promotion_usage_limit" (promotion_id) WHERE deleted_at IS NULL;`);
|
|
14
|
+
this.addSql(`CREATE INDEX IF NOT EXISTS "IDX_promotion_usage_limit_deleted_at" ON "promotion_usage_limit" (deleted_at) WHERE deleted_at IS NULL;`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
override async down(): Promise<void> {
|
|
18
|
+
this.addSql(`drop table if exists "custom_campaign_type" cascade;`);
|
|
19
|
+
|
|
20
|
+
this.addSql(`drop table if exists "promotion_usage_limit" cascade;`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20250526010310 extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`alter table if exists "promotion_usage_limit" drop constraint if exists "promotion_usage_limit_campaign_id_promotion_id_unique";`);
|
|
7
|
+
this.addSql(`drop index if exists "IDX_promotion_usage_limit_promotion_id_unique";`);
|
|
8
|
+
|
|
9
|
+
this.addSql(`alter table if exists "promotion_usage_limit" add column if not exists "campaign_id" text not null;`);
|
|
10
|
+
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_promotion_usage_limit_campaign_id_promotion_id_unique" ON "promotion_usage_limit" (campaign_id, promotion_id) WHERE deleted_at IS NULL;`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override async down(): Promise<void> {
|
|
14
|
+
this.addSql(`drop index if exists "IDX_promotion_usage_limit_campaign_id_promotion_id_unique";`);
|
|
15
|
+
this.addSql(`alter table if exists "promotion_usage_limit" drop column if exists "campaign_id";`);
|
|
16
|
+
|
|
17
|
+
this.addSql(`CREATE UNIQUE INDEX IF NOT EXISTS "IDX_promotion_usage_limit_promotion_id_unique" ON "promotion_usage_limit" (promotion_id) WHERE deleted_at IS NULL;`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Migration } from '@mikro-orm/migrations';
|
|
2
|
+
|
|
3
|
+
export class Migration20250529011904 extends Migration {
|
|
4
|
+
|
|
5
|
+
override async up(): Promise<void> {
|
|
6
|
+
this.addSql(`alter table if exists "promotion_usage_limit" add column if not exists "product_id" text not null;`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
override async down(): Promise<void> {
|
|
10
|
+
this.addSql(`alter table if exists "promotion_usage_limit" drop column if exists "product_id";`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { model } from "@medusajs/framework/utils";
|
|
2
|
+
import { CampaignTypeEnum } from "../types/campaign-type.enum";
|
|
3
|
+
|
|
4
|
+
const CustomCampaignType = model.define("custom_campaign_type", {
|
|
5
|
+
id: model.id().primaryKey(),
|
|
6
|
+
campaign_id: model.text().unique(),
|
|
7
|
+
type: model.enum(CampaignTypeEnum),
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export default CustomCampaignType;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { model } from "@medusajs/framework/utils";
|
|
2
|
+
|
|
3
|
+
const PromotionUsageLimit = model
|
|
4
|
+
.define("promotion_usage_limit", {
|
|
5
|
+
id: model.id().primaryKey(),
|
|
6
|
+
campaign_id: model.text(),
|
|
7
|
+
promotion_id: model.text(),
|
|
8
|
+
product_id: model.text(),
|
|
9
|
+
limit: model.number(),
|
|
10
|
+
used: model.number(),
|
|
11
|
+
})
|
|
12
|
+
.indexes([{ on: ["campaign_id", "promotion_id"], unique: true }]);
|
|
13
|
+
|
|
14
|
+
export default PromotionUsageLimit;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { MedusaService } from "@medusajs/framework/utils";
|
|
2
|
+
import CustomCampaignType from "./models/custom-campaign-type";
|
|
3
|
+
import PromotionUsageLimit from "./models/promotion-usage-limit";
|
|
4
|
+
|
|
5
|
+
class CustomCampaignModuleService extends MedusaService({
|
|
6
|
+
CustomCampaignType,
|
|
7
|
+
PromotionUsageLimit,
|
|
8
|
+
}) {}
|
|
9
|
+
|
|
10
|
+
export default CustomCampaignModuleService;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
## Module Providers
|
|
2
|
+
|
|
3
|
+
You can create module providers, such as Notification or File Module Providers under a sub-directory of this directory. For example, `src/providers/my-notification`.
|
|
4
|
+
|
|
5
|
+
Then, you register them in the Medusa application as `plugin-name/providers/my-notification`:
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
module.exports = defineConfig({
|
|
9
|
+
// ...
|
|
10
|
+
modules: [
|
|
11
|
+
{
|
|
12
|
+
resolve: "@medusajs/medusa/notification",
|
|
13
|
+
options: {
|
|
14
|
+
providers: [
|
|
15
|
+
{
|
|
16
|
+
resolve: "@myorg/plugin-name/providers/my-notification",
|
|
17
|
+
id: "my-notification",
|
|
18
|
+
options: {
|
|
19
|
+
channels: ["email"],
|
|
20
|
+
// provider options...
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
})
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Learn more in [this documentation](https://docs.medusajs.com/learn/fundamentals/plugins/create).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Custom subscribers
|
|
2
|
+
|
|
3
|
+
Subscribers handle events emitted in the Medusa application.
|
|
4
|
+
|
|
5
|
+
The subscriber is created in a TypeScript or JavaScript file under the `src/subscribers` directory.
|
|
6
|
+
|
|
7
|
+
For example, create the file `src/subscribers/product-created.ts` with the following content:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
type SubscriberConfig,
|
|
12
|
+
} from "@medusajs/framework"
|
|
13
|
+
|
|
14
|
+
// subscriber function
|
|
15
|
+
export default async function productCreateHandler() {
|
|
16
|
+
console.log("A product was created")
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// subscriber config
|
|
20
|
+
export const config: SubscriberConfig = {
|
|
21
|
+
event: "product.created",
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
A subscriber file must export:
|
|
26
|
+
|
|
27
|
+
- The subscriber function that is an asynchronous function executed whenever the associated event is triggered.
|
|
28
|
+
- A configuration object defining the event this subscriber is listening to.
|
|
29
|
+
|
|
30
|
+
## Subscriber Parameters
|
|
31
|
+
|
|
32
|
+
A subscriber receives an object having the following properties:
|
|
33
|
+
|
|
34
|
+
- `event`: An object holding the event's details. It has a `data` property, which is the event's data payload.
|
|
35
|
+
- `container`: The Medusa container. Use it to resolve modules' main services and other registered resources.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import type {
|
|
39
|
+
SubscriberArgs,
|
|
40
|
+
SubscriberConfig,
|
|
41
|
+
} from "@medusajs/framework"
|
|
42
|
+
|
|
43
|
+
export default async function productCreateHandler({
|
|
44
|
+
event: { data },
|
|
45
|
+
container,
|
|
46
|
+
}: SubscriberArgs<{ id: string }>) {
|
|
47
|
+
const productId = data.id
|
|
48
|
+
|
|
49
|
+
const productModuleService = container.resolve("product")
|
|
50
|
+
|
|
51
|
+
const product = await productModuleService.retrieveProduct(productId)
|
|
52
|
+
|
|
53
|
+
console.log(`The product ${product.title} was created`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const config: SubscriberConfig = {
|
|
57
|
+
event: "product.created",
|
|
58
|
+
}
|
|
59
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SubscriberArgs, type SubscriberConfig } from "@medusajs/framework";
|
|
2
|
+
import { updatePromotionUsageWorkflow } from "../workflows/custom-campaign/updatePromotionUsageWorkflow";
|
|
3
|
+
|
|
4
|
+
export default async function updatePromotionUsage({
|
|
5
|
+
event: { data },
|
|
6
|
+
}: SubscriberArgs<{ id: string }>) {
|
|
7
|
+
// update promotion usage
|
|
8
|
+
await updatePromotionUsageWorkflow.run({
|
|
9
|
+
input: {
|
|
10
|
+
order_id: data.id,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const config: SubscriberConfig = {
|
|
16
|
+
event: `order.placed`,
|
|
17
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Custom Workflows
|
|
2
|
+
|
|
3
|
+
A workflow is a series of queries and actions that complete a task.
|
|
4
|
+
|
|
5
|
+
The workflow is created in a TypeScript or JavaScript file under the `src/workflows` directory.
|
|
6
|
+
|
|
7
|
+
For example:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import {
|
|
11
|
+
createStep,
|
|
12
|
+
createWorkflow,
|
|
13
|
+
WorkflowResponse,
|
|
14
|
+
StepResponse,
|
|
15
|
+
} from "@medusajs/framework/workflows-sdk"
|
|
16
|
+
|
|
17
|
+
const step1 = createStep("step-1", async () => {
|
|
18
|
+
return new StepResponse(`Hello from step one!`)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
type WorkflowInput = {
|
|
22
|
+
name: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const step2 = createStep(
|
|
26
|
+
"step-2",
|
|
27
|
+
async ({ name }: WorkflowInput) => {
|
|
28
|
+
return new StepResponse(`Hello ${name} from step two!`)
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
type WorkflowOutput = {
|
|
33
|
+
message1: string
|
|
34
|
+
message2: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const helloWorldWorkflow = createWorkflow(
|
|
38
|
+
"hello-world",
|
|
39
|
+
(input: WorkflowInput) => {
|
|
40
|
+
const greeting1 = step1()
|
|
41
|
+
const greeting2 = step2(input)
|
|
42
|
+
|
|
43
|
+
return new WorkflowResponse({
|
|
44
|
+
message1: greeting1,
|
|
45
|
+
message2: greeting2
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
export default helloWorldWorkflow
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Execute Workflow
|
|
54
|
+
|
|
55
|
+
You can execute the workflow from other resources, such as API routes, scheduled jobs, or subscribers.
|
|
56
|
+
|
|
57
|
+
For example, to execute the workflow in an API route:
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import type {
|
|
61
|
+
MedusaRequest,
|
|
62
|
+
MedusaResponse,
|
|
63
|
+
} from "@medusajs/framework"
|
|
64
|
+
import myWorkflow from "../../../workflows/hello-world"
|
|
65
|
+
|
|
66
|
+
export async function GET(
|
|
67
|
+
req: MedusaRequest,
|
|
68
|
+
res: MedusaResponse
|
|
69
|
+
) {
|
|
70
|
+
const { result } = await myWorkflow(req.scope)
|
|
71
|
+
.run({
|
|
72
|
+
input: {
|
|
73
|
+
name: req.query.name as string,
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
res.send(result)
|
|
78
|
+
}
|
|
79
|
+
```
|