@lodashventure/medusa-campaign 1.3.12 → 1.4.0

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.
Files changed (54) hide show
  1. package/.medusa/server/src/admin/index.js +1238 -16
  2. package/.medusa/server/src/admin/index.mjs +1240 -18
  3. package/.medusa/server/src/api/admin/campaigns/[id]/detail/route.js +67 -0
  4. package/.medusa/server/src/api/admin/campaigns/[id]/image/route.js +80 -0
  5. package/.medusa/server/src/api/admin/campaigns/[id]/thumbnail/route.js +80 -0
  6. package/.medusa/server/src/api/admin/campaigns/sync/route.js +8 -6
  7. package/.medusa/server/src/api/admin/flash-sales/[id]/route.js +22 -4
  8. package/.medusa/server/src/api/middlewares.js +24 -1
  9. package/.medusa/server/src/api/store/buy-x-get-y/[id]/route.js +1 -1
  10. package/.medusa/server/src/api/store/buy-x-get-y/products/[productId]/route.js +1 -1
  11. package/.medusa/server/src/api/store/buy-x-get-y/route.js +3 -1
  12. package/.medusa/server/src/api/store/campaigns/[id]/route.js +40 -12
  13. package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251024000000.js +53 -0
  14. package/.medusa/server/src/modules/custom-campaigns/migrations/Migration20251025000000.js +22 -0
  15. package/.medusa/server/src/modules/custom-campaigns/models/campaign-detail.js +26 -0
  16. package/.medusa/server/src/modules/custom-campaigns/service.js +3 -1
  17. package/.medusa/server/src/subscribers/order-placed.js +2 -2
  18. package/.medusa/server/src/workflows/buy-x-get-y/applyBuyXGetYToCartWorkflow.js +16 -4
  19. package/.medusa/server/src/workflows/campaign-detail/update-campaign-detail.js +55 -0
  20. package/.medusa/server/src/workflows/campaign-detail/upload-campaign-images.js +120 -0
  21. package/.medusa/server/src/workflows/custom-campaign/createBuyXGetYCampaignWorkflow.js +13 -14
  22. package/package.json +7 -5
  23. package/src/admin/components/campaign-detail-form.tsx +407 -0
  24. package/src/admin/components/campaign-image-uploader.tsx +313 -0
  25. package/src/admin/components/markdown-editor.tsx +298 -0
  26. package/src/admin/routes/flash-sales/[id]/page.tsx +51 -14
  27. package/src/admin/widgets/campaign-detail-widget.tsx +299 -0
  28. package/src/admin/widgets/campaign-stats-widget.tsx +238 -0
  29. package/src/api/admin/campaigns/[id]/detail/route.ts +77 -0
  30. package/src/api/admin/campaigns/[id]/image/route.ts +87 -0
  31. package/src/api/admin/campaigns/[id]/thumbnail/route.ts +87 -0
  32. package/src/api/admin/campaigns/sync/route.ts +53 -28
  33. package/src/api/admin/flash-sales/[id]/route.ts +50 -19
  34. package/src/api/middlewares.ts +21 -0
  35. package/src/api/store/buy-x-get-y/[id]/route.ts +10 -10
  36. package/src/api/store/buy-x-get-y/products/[productId]/route.ts +11 -12
  37. package/src/api/store/buy-x-get-y/route.ts +12 -5
  38. package/src/api/store/campaigns/[id]/route.ts +54 -24
  39. package/src/modules/custom-campaigns/migrations/Migration20251024000000.ts +53 -0
  40. package/src/modules/custom-campaigns/migrations/Migration20251025000000.ts +19 -0
  41. package/src/modules/custom-campaigns/models/campaign-detail.ts +25 -0
  42. package/src/modules/custom-campaigns/service.ts +2 -0
  43. package/src/subscribers/order-placed.ts +0 -2
  44. package/src/types/index.d.ts +46 -0
  45. package/src/workflows/buy-x-get-y/applyBuyXGetYToCartWorkflow.ts +41 -18
  46. package/src/workflows/campaign-detail/update-campaign-detail.ts +85 -0
  47. package/src/workflows/campaign-detail/upload-campaign-images.ts +163 -0
  48. package/src/workflows/custom-campaign/createBuyXGetYCampaignWorkflow.ts +23 -22
  49. package/.medusa/server/src/api/admin/campaigns/fix-dates/route.js +0 -103
  50. package/.medusa/server/src/api/admin/force-fix/route.js +0 -176
  51. package/.medusa/server/src/api/admin/test-campaign/route.js +0 -132
  52. package/src/api/admin/campaigns/fix-dates/route.ts +0 -107
  53. package/src/api/admin/force-fix/route.ts +0 -184
  54. package/src/api/admin/test-campaign/route.ts +0 -141
@@ -0,0 +1,87 @@
1
+ import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
2
+ import { uploadCampaignImagesWorkflow } from "../../../../../workflows/campaign-detail/upload-campaign-images";
3
+ import { deleteFilesWorkflow } from "@medusajs/medusa/core-flows";
4
+ import { CUSTOM_CAMPAIGN_MODULE } from "../../../../../modules/custom-campaigns";
5
+ import CustomCampaignModuleService from "../../../../../modules/custom-campaigns/service";
6
+
7
+ // POST upload campaign thumbnail
8
+ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
9
+ const { id: campaign_id } = req.params;
10
+ const file = (req as any).file;
11
+
12
+ if (!file) {
13
+ return res.status(400).json({
14
+ error: "No file provided",
15
+ });
16
+ }
17
+
18
+ try {
19
+ const { result } = await uploadCampaignImagesWorkflow(req.scope).run({
20
+ input: {
21
+ campaign_id,
22
+ files: [
23
+ {
24
+ filename: Buffer.from(file.originalname, "latin1").toString("utf8"),
25
+ mimeType: file.mimetype,
26
+ content: file.buffer.toString("binary"),
27
+ access: "public" as const,
28
+ },
29
+ ],
30
+ image_type: "thumbnail",
31
+ },
32
+ });
33
+
34
+ res.status(200).json({ campaign_detail: result });
35
+ } catch (error) {
36
+ console.error("Error uploading campaign thumbnail:", error);
37
+ res.status(500).json({
38
+ error: "Failed to upload campaign thumbnail",
39
+ details: error instanceof Error ? error.message : String(error),
40
+ });
41
+ }
42
+ };
43
+
44
+ // DELETE campaign thumbnail
45
+ export const DELETE = async (req: MedusaRequest, res: MedusaResponse) => {
46
+ const { id: campaign_id } = req.params;
47
+ const customCampaignModuleService =
48
+ req.scope.resolve<CustomCampaignModuleService>(CUSTOM_CAMPAIGN_MODULE);
49
+
50
+ try {
51
+ const [campaignDetail] =
52
+ await customCampaignModuleService.listCampaignDetails({
53
+ campaign_id,
54
+ });
55
+
56
+ if (!campaignDetail || !campaignDetail.thumbnail_file_id) {
57
+ return res.status(404).json({
58
+ error: "Campaign thumbnail not found",
59
+ });
60
+ }
61
+
62
+ // Delete file
63
+ await deleteFilesWorkflow(req.scope).run({
64
+ input: {
65
+ ids: [campaignDetail.thumbnail_file_id],
66
+ },
67
+ });
68
+
69
+ // Update campaign detail
70
+ const [updatedDetail] =
71
+ await customCampaignModuleService.updateCampaignDetails([
72
+ {
73
+ id: campaignDetail.id,
74
+ thumbnail_url: null,
75
+ thumbnail_file_id: null,
76
+ },
77
+ ]);
78
+
79
+ res.status(200).json({ campaign_detail: updatedDetail });
80
+ } catch (error) {
81
+ console.error("Error deleting campaign thumbnail:", error);
82
+ res.status(500).json({
83
+ error: "Failed to delete campaign thumbnail",
84
+ details: error instanceof Error ? error.message : String(error),
85
+ });
86
+ }
87
+ };
@@ -19,12 +19,24 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
19
19
  console.log(`Found ${allCampaigns.length} total campaigns`);
20
20
 
21
21
  // Get existing custom campaign types
22
- const existingTypes = await customCampaignModuleService.listCustomCampaignTypes({});
23
- const existingCampaignIds = new Set(existingTypes.map((ct) => ct.campaign_id));
22
+ const existingTypes =
23
+ await customCampaignModuleService.listCustomCampaignTypes({});
24
+ const existingCampaignIds = new Set(
25
+ existingTypes.map((ct) => ct.campaign_id),
26
+ );
24
27
  console.log(`Found ${existingTypes.length} existing custom campaign types`);
25
28
 
26
- const created = [];
27
- const skipped = [];
29
+ const created: Array<{
30
+ id: string;
31
+ name: string | undefined;
32
+ type: CampaignTypeEnum;
33
+ custom_type_id: string;
34
+ }> = [];
35
+ const skipped: Array<{
36
+ id: string;
37
+ name: string | undefined;
38
+ reason: string;
39
+ }> = [];
28
40
 
29
41
  // Create custom campaign types for campaigns that don't have them
30
42
  for (const campaign of allCampaigns) {
@@ -42,18 +54,22 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
42
54
  let type = CampaignTypeEnum.FlashSale; // Default to flash sale
43
55
 
44
56
  const nameLower = campaign.name?.toLowerCase() || "";
45
- const identifierLower = campaign.campaign_identifier?.toLowerCase() || "";
46
-
47
- if (nameLower.includes("bogo") ||
48
- nameLower.includes("buy") && nameLower.includes("get") ||
49
- identifierLower.includes("buy-x-get-y")) {
57
+ const identifierLower =
58
+ campaign.campaign_identifier?.toLowerCase() || "";
59
+
60
+ if (
61
+ nameLower.includes("bogo") ||
62
+ (nameLower.includes("buy") && nameLower.includes("get")) ||
63
+ identifierLower.includes("buy-x-get-y")
64
+ ) {
50
65
  type = CampaignTypeEnum.BuyXGetY;
51
66
  }
52
67
 
53
- const result = await customCampaignModuleService.createCustomCampaignTypes({
54
- campaign_id: campaign.id,
55
- type: type,
56
- });
68
+ const result =
69
+ await customCampaignModuleService.createCustomCampaignTypes({
70
+ campaign_id: campaign.id,
71
+ type: type,
72
+ });
57
73
 
58
74
  created.push({
59
75
  id: campaign.id,
@@ -62,9 +78,14 @@ export const POST = async (req: MedusaRequest, res: MedusaResponse) => {
62
78
  custom_type_id: result.id,
63
79
  });
64
80
 
65
- console.log(`✅ Created custom type for campaign: ${campaign.name} (${type})`);
81
+ console.log(
82
+ `✅ Created custom type for campaign: ${campaign.name} (${type})`,
83
+ );
66
84
  } catch (error) {
67
- console.error(`❌ Failed to create custom type for campaign ${campaign.id}:`, error);
85
+ console.error(
86
+ `❌ Failed to create custom type for campaign ${campaign.id}:`,
87
+ error,
88
+ );
68
89
  skipped.push({
69
90
  id: campaign.id,
70
91
  name: campaign.name,
@@ -105,12 +126,11 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
105
126
  try {
106
127
  // Get all campaigns and custom types
107
128
  const allCampaigns = await promotionService.listCampaigns({});
108
- const customTypes = await customCampaignModuleService.listCustomCampaignTypes({});
129
+ const customTypes =
130
+ await customCampaignModuleService.listCustomCampaignTypes({});
109
131
 
110
132
  // Map custom types by campaign ID
111
- const typeMap = new Map(
112
- customTypes.map((ct) => [ct.campaign_id, ct])
113
- );
133
+ const typeMap = new Map(customTypes.map((ct) => [ct.campaign_id, ct]));
114
134
 
115
135
  // Check which campaigns have custom types
116
136
  const campaignStatus = allCampaigns.map((campaign: any) => {
@@ -121,15 +141,19 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
121
141
  name: campaign.name,
122
142
  campaign_identifier: campaign.campaign_identifier,
123
143
  hasCustomType: !!customType,
124
- customType: customType ? {
125
- id: customType.id,
126
- type: customType.type,
127
- } : null,
144
+ customType: customType
145
+ ? {
146
+ id: customType.id,
147
+ type: customType.type,
148
+ }
149
+ : null,
128
150
  };
129
151
  });
130
152
 
131
153
  const withCustomType = campaignStatus.filter((c: any) => c.hasCustomType);
132
- const withoutCustomType = campaignStatus.filter((c: any) => !c.hasCustomType);
154
+ const withoutCustomType = campaignStatus.filter(
155
+ (c: any) => !c.hasCustomType,
156
+ );
133
157
 
134
158
  res.status(200).json({
135
159
  summary: {
@@ -139,9 +163,10 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
139
163
  },
140
164
  withCustomType,
141
165
  withoutCustomType,
142
- recommendation: withoutCustomType.length > 0
143
- ? "Run POST /admin/campaigns/sync to create missing custom types"
144
- : "All campaigns have custom types",
166
+ recommendation:
167
+ withoutCustomType.length > 0
168
+ ? "Run POST /admin/campaigns/sync to create missing custom types"
169
+ : "All campaigns have custom types",
145
170
  });
146
171
  } catch (error) {
147
172
  console.error("Error checking sync status:", error);
@@ -150,4 +175,4 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
150
175
  details: error instanceof Error ? error.message : String(error),
151
176
  });
152
177
  }
153
- };
178
+ };
@@ -19,7 +19,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
19
19
  if (!id) {
20
20
  throw new MedusaError(
21
21
  MedusaError.Types.INVALID_DATA,
22
- "Campaign ID is required"
22
+ "Campaign ID is required",
23
23
  );
24
24
  }
25
25
 
@@ -38,24 +38,30 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
38
38
  campaign_id: id,
39
39
  });
40
40
 
41
- console.log(`Found ${customCampaignTypes.length} campaign types for ID ${id}`);
41
+ console.log(
42
+ `Found ${customCampaignTypes.length} campaign types for ID ${id}`,
43
+ );
42
44
 
43
45
  if (customCampaignTypes.length === 0) {
44
46
  throw new MedusaError(
45
47
  MedusaError.Types.NOT_FOUND,
46
- `Flash sale with ID ${id} not found`
48
+ `Flash sale with ID ${id} not found`,
47
49
  );
48
50
  }
49
51
 
50
52
  // Fetch the campaign from promotion service
51
53
  const campaign = await promotionService.retrieveCampaign(id, {
52
- relations: ["promotions", "promotions.application_method", "promotions.application_method.target_rules"],
54
+ relations: [
55
+ "promotions",
56
+ "promotions.application_method",
57
+ "promotions.application_method.target_rules",
58
+ ],
53
59
  });
54
60
 
55
61
  if (!campaign) {
56
62
  throw new MedusaError(
57
63
  MedusaError.Types.NOT_FOUND,
58
- `Flash sale with ID ${id} not found`
64
+ `Flash sale with ID ${id} not found`,
59
65
  );
60
66
  }
61
67
 
@@ -72,9 +78,23 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
72
78
  const promotionLimitsMap = new Map();
73
79
  promotionUsageLimits.forEach((limit) => {
74
80
  promotionLimitsMap.set(limit.promotion_id, limit);
75
- console.log(` Limit for promotion ${limit.promotion_id}: ${limit.limit} (used: ${limit.used})`);
81
+ console.log(
82
+ ` Limit for promotion ${limit.promotion_id}: ${limit.limit} (used: ${limit.used})`,
83
+ );
76
84
  });
77
85
 
86
+ // Fetch campaign details
87
+ console.log(`Fetching campaign details for campaign ${id}`);
88
+ const [campaignDetail] =
89
+ await customCampaignModuleService.listCampaignDetails({
90
+ campaign_id: id,
91
+ });
92
+ console.log(
93
+ campaignDetail
94
+ ? `Found campaign detail with image: ${campaignDetail.image_url}`
95
+ : "No campaign detail found",
96
+ );
97
+
78
98
  // Process promotions to extract product information
79
99
  const products: CustomCampaign["products"] = [];
80
100
  for await (const promotion of campaign.promotions ?? []) {
@@ -87,7 +107,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
87
107
 
88
108
  const productRule = promotion.application_method.target_rules.find(
89
109
  (rule) =>
90
- rule.attribute === "items.product.id" && rule.operator === "eq"
110
+ rule.attribute === "items.product.id" && rule.operator === "eq",
91
111
  );
92
112
  const promotionLimit = promotionLimitsMap.get(promotion.id);
93
113
 
@@ -97,13 +117,15 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
97
117
  }
98
118
 
99
119
  if (!promotionLimit.product_id) {
100
- console.log(` No product_id in usage limit for promotion ${promotion.id}`);
120
+ console.log(
121
+ ` No product_id in usage limit for promotion ${promotion.id}`,
122
+ );
101
123
  continue;
102
124
  }
103
125
 
104
126
  try {
105
127
  const product = await productService.retrieveProduct(
106
- promotionLimit.product_id
128
+ promotionLimit.product_id,
107
129
  );
108
130
 
109
131
  if (productRule && promotion.application_method.value !== undefined) {
@@ -112,22 +134,31 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
112
134
  id: product.id,
113
135
  title: product.title,
114
136
  },
115
- discountType: promotion.application_method?.type,
137
+ discountType: promotion.application_method?.type as "percentage",
116
138
  discountValue: promotion.application_method?.value,
117
- maxQty: promotion.application_method?.max_quantity,
118
- limit: promotionLimit?.limit,
139
+ maxQty: promotion.application_method?.max_quantity ?? 0,
140
+ limit: promotionLimit?.limit ?? 0,
119
141
  });
120
142
  console.log(` Added product ${product.title} to response`);
121
143
  }
122
144
  } catch (productError) {
123
- console.error(` Error fetching product ${promotionLimit.product_id}:`, productError);
145
+ console.error(
146
+ ` Error fetching product ${promotionLimit.product_id}:`,
147
+ productError,
148
+ );
124
149
  }
125
150
  }
126
151
 
127
152
  res.status(200).json({
128
153
  ...campaign,
154
+ name: campaign.name!,
155
+ description: campaign.description!,
156
+ starts_at: campaign.starts_at!,
157
+ ends_at: campaign.ends_at!,
158
+ type: CampaignTypeEnum.FlashSale,
129
159
  products,
130
- } satisfies CustomCampaign);
160
+ campaign_detail: campaignDetail || null,
161
+ } satisfies CustomCampaign & { campaign_detail: any });
131
162
  } catch (error) {
132
163
  if (error instanceof MedusaError) {
133
164
  throw error;
@@ -136,7 +167,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
136
167
  console.error("Error fetching flash sale:", error);
137
168
  throw new MedusaError(
138
169
  MedusaError.Types.UNEXPECTED_STATE,
139
- "An error occurred while fetching the flash sale"
170
+ "An error occurred while fetching the flash sale",
140
171
  );
141
172
  }
142
173
  };
@@ -146,7 +177,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
146
177
  */
147
178
  export const PUT = async (
148
179
  req: MedusaRequest<CustomCampaign>,
149
- res: MedusaResponse
180
+ res: MedusaResponse,
150
181
  ) => {
151
182
  const { id } = req.params;
152
183
  const body = req.body;
@@ -154,7 +185,7 @@ export const PUT = async (
154
185
  if (!id) {
155
186
  throw new MedusaError(
156
187
  MedusaError.Types.INVALID_DATA,
157
- "Campaign ID is required"
188
+ "Campaign ID is required",
158
189
  );
159
190
  }
160
191
 
@@ -163,7 +194,7 @@ export const PUT = async (
163
194
  if (new Date(body.ends_at) < new Date(body.starts_at)) {
164
195
  throw new MedusaError(
165
196
  MedusaError.Types.INVALID_DATA,
166
- "End date must be after start date"
197
+ "End date must be after start date",
167
198
  );
168
199
  }
169
200
 
@@ -184,7 +215,7 @@ export const PUT = async (
184
215
  console.error("Error updating flash sale:", error);
185
216
  throw new MedusaError(
186
217
  MedusaError.Types.UNEXPECTED_STATE,
187
- "An error occurred while updating the flash sale"
218
+ "An error occurred while updating the flash sale",
188
219
  );
189
220
  }
190
221
  };
@@ -7,6 +7,11 @@ import {
7
7
  createCustomCampaignSchema,
8
8
  GetFlashSalesSchema,
9
9
  } from "./admin/flash-sales/route";
10
+ import multer from "multer";
11
+
12
+ const upload = multer({
13
+ storage: multer.memoryStorage(),
14
+ });
10
15
 
11
16
  export default defineMiddlewares([
12
17
  {
@@ -29,4 +34,20 @@ export default defineMiddlewares([
29
34
  }),
30
35
  ],
31
36
  },
37
+ {
38
+ methods: ["POST"],
39
+ matcher: "/admin/campaigns/:id/image",
40
+ middlewares: [
41
+ // @ts-ignore
42
+ upload.single("file"),
43
+ ],
44
+ },
45
+ {
46
+ methods: ["POST"],
47
+ matcher: "/admin/campaigns/:id/thumbnail",
48
+ middlewares: [
49
+ // @ts-ignore
50
+ upload.single("file"),
51
+ ],
52
+ },
32
53
  ]);
@@ -13,7 +13,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
13
13
  if (!id) {
14
14
  throw new MedusaError(
15
15
  MedusaError.Types.INVALID_DATA,
16
- "Campaign ID is required"
16
+ "Campaign ID is required",
17
17
  );
18
18
  }
19
19
 
@@ -35,7 +35,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
35
35
  if (customCampaignTypes.length === 0) {
36
36
  throw new MedusaError(
37
37
  MedusaError.Types.NOT_FOUND,
38
- `Buy X Get Y campaign with ID ${id} not found`
38
+ `Buy X Get Y campaign with ID ${id} not found`,
39
39
  );
40
40
  }
41
41
 
@@ -45,19 +45,19 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
45
45
  if (!campaign) {
46
46
  throw new MedusaError(
47
47
  MedusaError.Types.NOT_FOUND,
48
- `Buy X Get Y campaign with ID ${id} not found`
48
+ `Buy X Get Y campaign with ID ${id} not found`,
49
49
  );
50
50
  }
51
51
 
52
52
  // Check if campaign is active
53
- const startsAt = new Date(campaign.starts_at);
54
- const endsAt = new Date(campaign.ends_at);
53
+ const startsAt = new Date(campaign.starts_at!);
54
+ const endsAt = new Date(campaign.ends_at!);
55
55
  const isActive = startsAt <= now && endsAt >= now;
56
56
 
57
57
  if (!isActive) {
58
58
  throw new MedusaError(
59
59
  MedusaError.Types.NOT_ALLOWED,
60
- "This campaign is not currently active"
60
+ "This campaign is not currently active",
61
61
  );
62
62
  }
63
63
 
@@ -69,13 +69,13 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
69
69
 
70
70
  // Filter out configs that have reached their limit
71
71
  const availableConfigs = buyXGetYConfigs.filter(
72
- (config) => !config.limit || config.used < config.limit
72
+ (config) => !config.limit || config.used < config.limit,
73
73
  );
74
74
 
75
75
  if (availableConfigs.length === 0) {
76
76
  throw new MedusaError(
77
77
  MedusaError.Types.NOT_ALLOWED,
78
- "This campaign has no available promotions"
78
+ "This campaign has no available promotions",
79
79
  );
80
80
  }
81
81
 
@@ -119,7 +119,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
119
119
  rewardValue: config.reward_value,
120
120
  remaining: config.limit ? config.limit - config.used : null,
121
121
  };
122
- })
122
+ }),
123
123
  );
124
124
 
125
125
  res.status(200).json({
@@ -140,7 +140,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
140
140
  console.error("Error fetching Buy X Get Y campaign:", error);
141
141
  throw new MedusaError(
142
142
  MedusaError.Types.UNEXPECTED_STATE,
143
- "An error occurred while fetching the campaign"
143
+ "An error occurred while fetching the campaign",
144
144
  );
145
145
  }
146
146
  };
@@ -2,7 +2,6 @@ import { container, MedusaRequest, MedusaResponse } from "@medusajs/framework";
2
2
  import { MedusaError, Modules } from "@medusajs/framework/utils";
3
3
  import { CUSTOM_CAMPAIGN_MODULE } from "../../../../../modules/custom-campaigns";
4
4
  import CustomCampaignModuleService from "../../../../../modules/custom-campaigns/service";
5
- import { CampaignTypeEnum } from "../../../../../modules/custom-campaigns/types/campaign-type.enum";
6
5
 
7
6
  /**
8
7
  * GET handler to check if a product has any active BOGO promotions
@@ -14,7 +13,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
14
13
  if (!productId) {
15
14
  throw new MedusaError(
16
15
  MedusaError.Types.INVALID_DATA,
17
- "Product ID is required"
16
+ "Product ID is required",
18
17
  );
19
18
  }
20
19
 
@@ -46,7 +45,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
46
45
 
47
46
  // Get campaign details for each config
48
47
  const campaignIds = Array.from(
49
- new Set(buyXGetYConfigs.map((config) => config.campaign_id))
48
+ new Set(buyXGetYConfigs.map((config) => config.campaign_id)),
50
49
  );
51
50
 
52
51
  const campaigns = await promotionService.listCampaigns({
@@ -61,14 +60,14 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
61
60
  const endsAt = new Date(campaign.ends_at);
62
61
  return startsAt <= now && endsAt >= now;
63
62
  })
64
- .map((campaign: any) => campaign.id)
63
+ .map((campaign: any) => campaign.id),
65
64
  );
66
65
 
67
66
  // Filter configs by active campaigns and availability
68
67
  const activeConfigs = buyXGetYConfigs.filter(
69
68
  (config) =>
70
69
  activeCampaignIds.has(config.campaign_id) &&
71
- (!config.limit || config.used < config.limit)
70
+ (!config.limit || config.used < config.limit),
72
71
  );
73
72
 
74
73
  if (activeConfigs.length === 0) {
@@ -82,19 +81,19 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
82
81
  const promotions = await Promise.all(
83
82
  activeConfigs.map(async (config) => {
84
83
  const campaign = campaigns.find(
85
- (c: any) => c.id === config.campaign_id
84
+ (c: any) => c.id === config.campaign_id,
86
85
  );
87
86
 
88
87
  const rewardProduct = await productService.retrieveProduct(
89
88
  config.reward_product_id,
90
89
  {
91
90
  select: ["id", "title", "thumbnail", "handle"],
92
- }
91
+ },
93
92
  );
94
93
 
95
94
  return {
96
- campaign_id: campaign.id,
97
- campaign_name: campaign.name,
95
+ campaign_id: campaign!.id,
96
+ campaign_name: campaign!.name,
98
97
  trigger_quantity: config.trigger_quantity,
99
98
  reward_product: {
100
99
  id: rewardProduct.id,
@@ -106,9 +105,9 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
106
105
  reward_type: config.reward_type,
107
106
  reward_value: config.reward_value,
108
107
  remaining: config.limit ? config.limit - config.used : null,
109
- ends_at: campaign.ends_at,
108
+ ends_at: campaign!.ends_at,
110
109
  };
111
- })
110
+ }),
112
111
  );
113
112
 
114
113
  res.status(200).json({
@@ -123,7 +122,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
123
122
  console.error("Error checking product BOGO promotions:", error);
124
123
  throw new MedusaError(
125
124
  MedusaError.Types.UNEXPECTED_STATE,
126
- "An error occurred while checking promotions"
125
+ "An error occurred while checking promotions",
127
126
  );
128
127
  }
129
128
  };
@@ -15,6 +15,8 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
15
15
 
16
16
  try {
17
17
  const now = new Date();
18
+ const take = parseInt(req.query.limit as string) || 20;
19
+ const skip = parseInt(req.query.offset as string) || 0;
18
20
 
19
21
  // Get all Buy X Get Y campaign types
20
22
  const customCampaignTypes =
@@ -22,7 +24,9 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
22
24
  type: CampaignTypeEnum.BuyXGetY,
23
25
  });
24
26
 
25
- console.log(`Found ${customCampaignTypes.length} Buy X Get Y campaign types`);
27
+ console.log(
28
+ `Found ${customCampaignTypes.length} Buy X Get Y campaign types`,
29
+ );
26
30
 
27
31
  if (customCampaignTypes.length === 0) {
28
32
  return res.status(200).json({
@@ -59,7 +63,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
59
63
 
60
64
  // Filter out configs that have reached their limit
61
65
  const availableConfigs = buyXGetYConfigs.filter(
62
- (config) => !config.limit || config.used < config.limit
66
+ (config) => !config.limit || config.used < config.limit,
63
67
  );
64
68
 
65
69
  if (availableConfigs.length === 0) {
@@ -98,7 +102,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
98
102
  rewardValue: config.reward_value,
99
103
  remaining: config.limit ? config.limit - config.used : null,
100
104
  };
101
- })
105
+ }),
102
106
  );
103
107
 
104
108
  return {
@@ -109,7 +113,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
109
113
  ends_at: campaign.ends_at,
110
114
  rules,
111
115
  };
112
- })
116
+ }),
113
117
  );
114
118
 
115
119
  // Filter out null campaigns (those with no available rules)
@@ -122,7 +126,10 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
122
126
  offset: skip,
123
127
  });
124
128
  } catch (error) {
125
- console.error("Error fetching Buy X Get Y campaigns for storefront:", error);
129
+ console.error(
130
+ "Error fetching Buy X Get Y campaigns for storefront:",
131
+ error,
132
+ );
126
133
  res.status(500).json({
127
134
  campaigns: [],
128
135
  count: 0,