@lodashventure/medusa-campaign 1.0.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.
Files changed (45) hide show
  1. package/.medusa/server/src/admin/index.js +2203 -273
  2. package/.medusa/server/src/admin/index.mjs +2196 -267
  3. package/.medusa/server/src/workflows/index.js +10 -0
  4. package/package.json +4 -4
  5. package/src/admin/README.md +31 -0
  6. package/src/admin/components/FlashSaleForm.tsx +379 -0
  7. package/src/admin/components/FlashSalePage.tsx +113 -0
  8. package/src/admin/components/ProductSelector.tsx +88 -0
  9. package/src/admin/hooks/useFlashSaleById.ts +21 -0
  10. package/src/admin/hooks/useFlashSales.ts +25 -0
  11. package/src/admin/lib/sdk.ts +10 -0
  12. package/src/admin/routes/flash-sales/[id]/page.tsx +105 -0
  13. package/src/admin/routes/flash-sales/create/page.tsx +51 -0
  14. package/src/admin/routes/flash-sales/page.tsx +15 -0
  15. package/src/admin/tsconfig.json +24 -0
  16. package/src/admin/types/campaign.ts +25 -0
  17. package/src/admin/vite-env.d.ts +1 -0
  18. package/src/api/README.md +133 -0
  19. package/src/api/admin/flash-sales/[id]/route.ts +164 -0
  20. package/src/api/admin/flash-sales/route.ts +87 -0
  21. package/src/api/middlewares.ts +32 -0
  22. package/src/api/store/campaigns/[id]/route.ts +133 -0
  23. package/src/api/store/campaigns/route.ts +36 -0
  24. package/src/jobs/README.md +36 -0
  25. package/src/links/README.md +26 -0
  26. package/src/links/campaign-type.ts +8 -0
  27. package/src/modules/README.md +116 -0
  28. package/src/modules/custom-campaigns/index.ts +8 -0
  29. package/src/modules/custom-campaigns/migrations/.snapshot-medusa-custom-campaign.json +235 -0
  30. package/src/modules/custom-campaigns/migrations/Migration20250524150901.ts +23 -0
  31. package/src/modules/custom-campaigns/migrations/Migration20250526010310.ts +20 -0
  32. package/src/modules/custom-campaigns/migrations/Migration20250529011904.ts +13 -0
  33. package/src/modules/custom-campaigns/models/custom-campaign-type.ts +10 -0
  34. package/src/modules/custom-campaigns/models/promotion-usage-limit.ts +14 -0
  35. package/src/modules/custom-campaigns/service.ts +10 -0
  36. package/src/modules/custom-campaigns/types/campaign-type.enum.ts +3 -0
  37. package/src/providers/README.md +30 -0
  38. package/src/subscribers/README.md +59 -0
  39. package/src/subscribers/order-placed.ts +17 -0
  40. package/src/workflows/README.md +79 -0
  41. package/src/workflows/custom-campaign/createCustomCampaignWorkflow.ts +181 -0
  42. package/src/workflows/custom-campaign/updateCustomFlashSaleWorkflow.ts +185 -0
  43. package/src/workflows/custom-campaign/updatePromotionUsageWorkflow.ts +70 -0
  44. package/src/workflows/hooks/deletePromotionOnCampaignDelete.ts +49 -0
  45. package/src/workflows/index.ts +3 -0
@@ -0,0 +1,10 @@
1
+ // src/admin/lib/sdk.ts
2
+ import Medusa from "@medusajs/js-sdk";
3
+
4
+ export const sdk = new Medusa({
5
+ baseUrl: import.meta.env.VITE_BACKEND_URL || "/",
6
+ debug: import.meta.env.DEV,
7
+ auth: {
8
+ type: "session",
9
+ },
10
+ });
@@ -0,0 +1,105 @@
1
+ import { defineRouteConfig } from "@medusajs/admin-sdk";
2
+ import { Sparkles } from "@medusajs/icons";
3
+ import { Button, Container, toast } from "@medusajs/ui";
4
+ import { useQueryClient } from "@tanstack/react-query";
5
+ import axios from "axios";
6
+ import { FC, useState } from "react";
7
+ import { useNavigate, useParams } from "react-router-dom";
8
+ import {
9
+ CampaignData,
10
+ CustomCampaign,
11
+ FlashSaleForm,
12
+ } from "../../../components/FlashSaleForm";
13
+ import { useFlashSaleById } from "../../../hooks/useFlashSaleById";
14
+
15
+ const FlashSaleDetail: FC = () => {
16
+ const { id } = useParams<{ id: string }>();
17
+ const navigate = useNavigate();
18
+ const queryClient = useQueryClient();
19
+ const { data, isLoading } = useFlashSaleById(id || "");
20
+
21
+ const [isEditing, setIsEditing] = useState(false);
22
+
23
+ async function handleSubmit(campaignData: CampaignData) {
24
+ try {
25
+ await axios.put(`/admin/flash-sales/${id}`, {
26
+ ...campaignData,
27
+ starts_at: new Date(campaignData.starts_at).toUTCString(),
28
+ ends_at: new Date(campaignData.ends_at).toUTCString(),
29
+ });
30
+
31
+ toast.success("Flash sale updated successfully");
32
+ queryClient.invalidateQueries({
33
+ exact: false,
34
+ predicate(query) {
35
+ return ["flash-sales", "flash-sale"].includes(
36
+ query.queryKey[0] as string
37
+ );
38
+ },
39
+ });
40
+ navigate("/flash-sales");
41
+ } catch (error) {
42
+ let message: string;
43
+
44
+ if (axios.isAxiosError(error)) {
45
+ console.log(error);
46
+ message = error.response?.data.message;
47
+ } else if (error instanceof Error) {
48
+ message = error.message;
49
+ } else {
50
+ message = "Failed to update flash sale";
51
+ }
52
+
53
+ toast.error("Failed to update flash sale", {
54
+ description: message,
55
+ });
56
+ }
57
+ }
58
+
59
+ if (isLoading) {
60
+ return (
61
+ <Container className="flex items-center justify-center h-screen">
62
+ <div className="animate-spin h-10 w-10 border-4 border-primary rounded-full border-t-transparent"></div>
63
+ </Container>
64
+ );
65
+ }
66
+
67
+ if (!data) {
68
+ return (
69
+ <Container>
70
+ <p>Flash sale not found</p>
71
+ </Container>
72
+ );
73
+ }
74
+
75
+ // Convert campaign data to the format expected by the form
76
+ const campaignData: CustomCampaign = {
77
+ name: data.name || "",
78
+ description: data.description || "",
79
+ type: "flash-sale",
80
+ starts_at: data.starts_at,
81
+ ends_at: data.ends_at,
82
+ };
83
+
84
+ return (
85
+ <>
86
+ <FlashSaleForm
87
+ initialData={campaignData}
88
+ initialProducts={
89
+ new Map(data.products.map((product) => [product.product.id, product]))
90
+ }
91
+ onSubmit={handleSubmit}
92
+ onCancel={() => navigate("/flash-sales")}
93
+ disabled={!isEditing}
94
+ />
95
+ <Button onClick={() => setIsEditing((prev) => !prev)}>Toggle Edit</Button>
96
+ </>
97
+ );
98
+ };
99
+
100
+ export const config = defineRouteConfig({
101
+ label: "Flash Sale Detail",
102
+ icon: Sparkles,
103
+ });
104
+
105
+ export default FlashSaleDetail;
@@ -0,0 +1,51 @@
1
+ import { toast } from "@medusajs/ui";
2
+ import { useQueryClient } from "@tanstack/react-query";
3
+ import axios from "axios";
4
+ import { FC } from "react";
5
+ import { useNavigate } from "react-router-dom";
6
+ import { CampaignData, FlashSaleForm } from "../../../components/FlashSaleForm";
7
+
8
+ const FlashSaleCreate: FC = () => {
9
+ const navigate = useNavigate();
10
+ const queryClient = useQueryClient();
11
+
12
+ async function handleSubmit(campaignData: CampaignData) {
13
+ try {
14
+ await axios.post("/admin/flash-sales", {
15
+ ...campaignData,
16
+ starts_at: new Date(campaignData.starts_at).toUTCString(),
17
+ ends_at: new Date(campaignData.ends_at).toUTCString(),
18
+ });
19
+ toast.success("Flash sale created successfully");
20
+ queryClient.invalidateQueries({
21
+ exact: false,
22
+ queryKey: ["flash-sales"],
23
+ });
24
+ navigate("/flash-sales");
25
+ } catch (error) {
26
+ let message: string;
27
+
28
+ if (axios.isAxiosError(error)) {
29
+ console.log(error);
30
+ message = error.response?.data.message;
31
+ } else if (error instanceof Error) {
32
+ message = error.message;
33
+ } else {
34
+ message = "Failed to create flash sale";
35
+ }
36
+
37
+ toast.error("Failed to create flash sale", {
38
+ description: message,
39
+ });
40
+ }
41
+ }
42
+
43
+ return (
44
+ <FlashSaleForm
45
+ onSubmit={handleSubmit}
46
+ onCancel={() => navigate("/flash-sales")}
47
+ />
48
+ );
49
+ };
50
+
51
+ export default FlashSaleCreate;
@@ -0,0 +1,15 @@
1
+ import { defineRouteConfig } from "@medusajs/admin-sdk";
2
+ import { Sparkles } from "@medusajs/icons";
3
+ import { FC } from "react";
4
+ import { FlashSalePage } from "../../components/FlashSalePage";
5
+
6
+ const FlashSale: FC = () => {
7
+ return <FlashSalePage />;
8
+ };
9
+
10
+ export const config = defineRouteConfig({
11
+ label: "Flash Sale",
12
+ icon: Sparkles,
13
+ });
14
+
15
+ export default FlashSale;
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+
9
+ /* Bundler mode */
10
+ "moduleResolution": "bundler",
11
+ "allowImportingTsExtensions": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "noEmit": true,
15
+ "jsx": "react-jsx",
16
+
17
+ /* Linting */
18
+ "strict": true,
19
+ "noUnusedLocals": true,
20
+ "noUnusedParameters": true,
21
+ "noFallthroughCasesInSwitch": true
22
+ },
23
+ "include": ["."]
24
+ }
@@ -0,0 +1,25 @@
1
+ export interface Campaign {
2
+ id: string;
3
+ name: string;
4
+ description: string;
5
+ start_date: string;
6
+ end_date: string;
7
+ status: "draft" | "active" | "completed" | "cancelled";
8
+ created_at: string;
9
+ updated_at: string;
10
+ }
11
+
12
+ export interface CampaignFilters {
13
+ status?: string[];
14
+ q?: string;
15
+ start_date?: string;
16
+ end_date?: string;
17
+ }
18
+
19
+ export interface CampaignFormValues {
20
+ name: string;
21
+ description: string;
22
+ start_date: string;
23
+ end_date: string;
24
+ status: string;
25
+ }
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,133 @@
1
+ # Custom API Routes
2
+
3
+ An API Route is a REST API endpoint.
4
+
5
+ An API Route is created in a TypeScript or JavaScript file under the `/src/api` directory of your Medusa application. The file’s name must be `route.ts` or `route.js`.
6
+
7
+ For example, to create a `GET` API Route at `/store/hello-world`, create the file `src/api/store/hello-world/route.ts` with the following content:
8
+
9
+ ```ts
10
+ import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
11
+
12
+ export async function GET(req: MedusaRequest, res: MedusaResponse) {
13
+ res.json({
14
+ message: "Hello world!",
15
+ });
16
+ }
17
+ ```
18
+
19
+ ## Supported HTTP methods
20
+
21
+ The file based routing supports the following HTTP methods:
22
+
23
+ - GET
24
+ - POST
25
+ - PUT
26
+ - PATCH
27
+ - DELETE
28
+ - OPTIONS
29
+ - HEAD
30
+
31
+ 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.
32
+
33
+ For example:
34
+
35
+ ```ts
36
+ import type { MedusaRequest, MedusaResponse } from "@medusajs/framework/http";
37
+
38
+ export async function GET(req: MedusaRequest, res: MedusaResponse) {
39
+ // Handle GET requests
40
+ }
41
+
42
+ export async function POST(req: MedusaRequest, res: MedusaResponse) {
43
+ // Handle POST requests
44
+ }
45
+
46
+ export async function PUT(req: MedusaRequest, res: MedusaResponse) {
47
+ // Handle PUT requests
48
+ }
49
+ ```
50
+
51
+ ## Parameters
52
+
53
+ To create an API route that accepts a path parameter, create a directory within the route's path whose name is of the format `[param]`.
54
+
55
+ 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`:
56
+
57
+ ```ts
58
+ import type {
59
+ MedusaRequest,
60
+ MedusaResponse,
61
+ } from "@medusajs/framework/http"
62
+
63
+ export async function GET(req: MedusaRequest, res: MedusaResponse) {
64
+ const { productId } = req.params;
65
+
66
+ res.json({
67
+ message: `You're looking for product ${productId}`
68
+ })
69
+ }
70
+ ```
71
+
72
+ To create an API route that accepts multiple path parameters, create within the file's path multiple directories whose names are of the format `[param]`.
73
+
74
+ 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`.
75
+
76
+ ## Using the container
77
+
78
+ The Medusa container is available on `req.scope`. Use it to access modules' main services and other registered resources:
79
+
80
+ ```ts
81
+ import type {
82
+ MedusaRequest,
83
+ MedusaResponse,
84
+ } from "@medusajs/framework/http"
85
+
86
+ export const GET = async (
87
+ req: MedusaRequest,
88
+ res: MedusaResponse
89
+ ) => {
90
+ const productModuleService = req.scope.resolve("product")
91
+
92
+ const [, count] = await productModuleService.listAndCount()
93
+
94
+ res.json({
95
+ count,
96
+ })
97
+ }
98
+ ```
99
+
100
+ ## Middleware
101
+
102
+ You can apply middleware to your routes by creating a file called `/api/middlewares.ts`. This file must export a configuration object with what middleware you want to apply to which routes.
103
+
104
+ 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:
105
+
106
+ ```ts
107
+ import { defineMiddlewares } from "@medusajs/framework/http"
108
+ import type {
109
+ MedusaRequest,
110
+ MedusaResponse,
111
+ MedusaNextFunction,
112
+ } from "@medusajs/framework/http";
113
+
114
+ async function logger(
115
+ req: MedusaRequest,
116
+ res: MedusaResponse,
117
+ next: MedusaNextFunction
118
+ ) {
119
+ console.log("Request received");
120
+ next();
121
+ }
122
+
123
+ export default defineMiddlewares({
124
+ routes: [
125
+ {
126
+ matcher: "/store/custom",
127
+ middlewares: [logger],
128
+ },
129
+ ],
130
+ })
131
+ ```
132
+
133
+ The `matcher` property can be either a string or a regular expression. The `middlewares` property accepts an array of middleware functions.
@@ -0,0 +1,164 @@
1
+ import { container, MedusaRequest, MedusaResponse } from "@medusajs/framework";
2
+ import {
3
+ ContainerRegistrationKeys,
4
+ MedusaError,
5
+ Modules,
6
+ } from "@medusajs/framework/utils";
7
+ import { CampaignTypeEnum } from "../../../../modules/custom-campaigns/types/campaign-type.enum";
8
+ import { CustomCampaign, createCustomCampaignSchema } from "../route";
9
+ import { updateCustomFlashSaleWorkflow } from "../../../../workflows/custom-campaign/updateCustomFlashSaleWorkflow";
10
+
11
+ /**
12
+ * GET handler for fetching a specific flash sale by ID
13
+ */
14
+ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
15
+ const { id } = req.params;
16
+
17
+ if (!id) {
18
+ throw new MedusaError(
19
+ MedusaError.Types.INVALID_DATA,
20
+ "Campaign ID is required"
21
+ );
22
+ }
23
+
24
+ const query = container.resolve(ContainerRegistrationKeys.QUERY);
25
+ const productService = container.resolve(Modules.PRODUCT);
26
+
27
+ try {
28
+ // First, find the custom campaign type by campaign ID
29
+ const {
30
+ data: [customCampaignTypes],
31
+ } = await query.graph({
32
+ entity: "custom_campaign_type",
33
+ fields: [
34
+ "id",
35
+ "campaign.*",
36
+ "campaign.promotions.*",
37
+ "campaign.promotions.application_method.*",
38
+ "campaign.promotions.application_method.target_rules.*",
39
+ ],
40
+ filters: {
41
+ type: CampaignTypeEnum.FlashSale,
42
+ campaign_id: id,
43
+ },
44
+ });
45
+
46
+ const campaign = customCampaignTypes.campaign;
47
+
48
+ if (!campaign) {
49
+ throw new MedusaError(
50
+ MedusaError.Types.NOT_FOUND,
51
+ `Flash sale with ID ${id} not found`
52
+ );
53
+ }
54
+
55
+ // Fetch promotion usage limits for the campaign
56
+ const { data: promotionUsageLimits } = await query.graph({
57
+ entity: "promotion_usage_limit",
58
+ fields: ["id", "promotion_id", "product_id", "limit", "used"],
59
+ filters: {
60
+ campaign_id: id,
61
+ },
62
+ });
63
+
64
+ // Create a map of promotion usage limits by promotion ID
65
+ const promotionLimitsMap = new Map();
66
+ promotionUsageLimits.forEach((limit) => {
67
+ promotionLimitsMap.set(limit.promotion_id, limit);
68
+ });
69
+
70
+ // Process promotions to extract product information
71
+ const products: CustomCampaign["products"] = [];
72
+ for await (const promotion of campaign.promotions ?? []) {
73
+ if (!promotion.application_method?.target_rules?.length) {
74
+ continue;
75
+ }
76
+
77
+ const productRule = promotion.application_method.target_rules.find(
78
+ (rule) =>
79
+ rule.attribute === "items.product.id" && rule.operator === "eq"
80
+ );
81
+ const promotionLimit = promotionLimitsMap.get(promotion.id);
82
+
83
+ const product = await productService.retrieveProduct(
84
+ promotionLimit?.product_id
85
+ );
86
+
87
+ if (productRule && promotion.application_method.value) {
88
+ products.push({
89
+ product: {
90
+ id: product.id,
91
+ title: product.title,
92
+ },
93
+ discountType: promotion.application_method?.type,
94
+ discountValue: promotion.application_method?.value,
95
+ maxQty: promotion.application_method?.max_quantity,
96
+ limit: promotionLimitsMap.get(promotion.id)?.limit,
97
+ });
98
+ }
99
+ }
100
+
101
+ res.status(200).json({
102
+ ...campaign,
103
+ products,
104
+ } satisfies CustomCampaign);
105
+ } catch (error) {
106
+ if (error instanceof MedusaError) {
107
+ throw error;
108
+ }
109
+
110
+ console.error("Error fetching flash sale:", error);
111
+ throw new MedusaError(
112
+ MedusaError.Types.UNEXPECTED_STATE,
113
+ "An error occurred while fetching the flash sale"
114
+ );
115
+ }
116
+ };
117
+
118
+ /**
119
+ * PUT handler for updating a specific flash sale by ID
120
+ */
121
+ export const PUT = async (
122
+ req: MedusaRequest<CustomCampaign>,
123
+ res: MedusaResponse
124
+ ) => {
125
+ const { id } = req.params;
126
+ const body = req.body;
127
+
128
+ if (!id) {
129
+ throw new MedusaError(
130
+ MedusaError.Types.INVALID_DATA,
131
+ "Campaign ID is required"
132
+ );
133
+ }
134
+
135
+ try {
136
+ // Check if start date is before end date
137
+ if (new Date(body.ends_at) < new Date(body.starts_at)) {
138
+ throw new MedusaError(
139
+ MedusaError.Types.INVALID_DATA,
140
+ "End date must be after start date"
141
+ );
142
+ }
143
+
144
+ // Update the flash sale
145
+ const result = await updateCustomFlashSaleWorkflow.run({
146
+ input: {
147
+ ...body,
148
+ id,
149
+ },
150
+ });
151
+
152
+ res.status(200).json(result.result);
153
+ } catch (error) {
154
+ if (error instanceof MedusaError) {
155
+ throw error;
156
+ }
157
+
158
+ console.error("Error updating flash sale:", error);
159
+ throw new MedusaError(
160
+ MedusaError.Types.UNEXPECTED_STATE,
161
+ "An error occurred while updating the flash sale"
162
+ );
163
+ }
164
+ };
@@ -0,0 +1,87 @@
1
+ import { container, MedusaRequest, MedusaResponse } from "@medusajs/framework";
2
+ import {
3
+ ContainerRegistrationKeys,
4
+ MedusaError,
5
+ } from "@medusajs/framework/utils";
6
+ import { createFindParams } from "@medusajs/medusa/api/utils/validators";
7
+ import z from "zod";
8
+ import { CampaignTypeEnum } from "../../../modules/custom-campaigns/types/campaign-type.enum";
9
+ import { createCustomCampaignWorkflow } from "../../../workflows/custom-campaign/createCustomCampaignWorkflow";
10
+
11
+ export const createCustomCampaignSchema = z.object({
12
+ name: z.string().min(1, "Name is required"),
13
+ description: z.string().min(1, "Description is required"),
14
+ type: z.nativeEnum(CampaignTypeEnum),
15
+ starts_at: z.coerce.date(),
16
+ ends_at: z.coerce.date(),
17
+ products: z
18
+ .array(
19
+ z.object({
20
+ product: z.object({
21
+ id: z.string(),
22
+ title: z.string(),
23
+ }),
24
+ discountType: z.enum([
25
+ "percentage",
26
+ // need to handle fixed discount and currency
27
+ // "fixed",
28
+ ]),
29
+ discountValue: z.number().min(1),
30
+ limit: z.number().min(1),
31
+ maxQty: z.number().min(1),
32
+ })
33
+ )
34
+ .min(1, "At least one product is required"),
35
+ });
36
+
37
+ export type CustomCampaign = z.infer<typeof createCustomCampaignSchema>;
38
+
39
+ export const GetFlashSalesSchema = createFindParams({
40
+ order: "-created_at",
41
+ });
42
+
43
+ export const POST = async (
44
+ req: MedusaRequest<CustomCampaign>,
45
+ res: MedusaResponse
46
+ ) => {
47
+ const body = req.body;
48
+
49
+ if (new Date(body.ends_at) < new Date(body.starts_at)) {
50
+ throw new MedusaError(
51
+ MedusaError.Types.INVALID_DATA,
52
+ "End date must be after start date"
53
+ );
54
+ }
55
+
56
+ const customCampaign = await createCustomCampaignWorkflow.run({
57
+ input: body,
58
+ });
59
+
60
+ res.status(200).json(customCampaign);
61
+ };
62
+
63
+ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
64
+ const query = container.resolve(ContainerRegistrationKeys.QUERY);
65
+ const {
66
+ data: customCampaigns,
67
+ metadata: { count, take, skip } = {
68
+ count: 0,
69
+ take: 20,
70
+ skip: 0,
71
+ },
72
+ } = await query.graph({
73
+ entity: "custom_campaign_type",
74
+ ...req.queryConfig,
75
+ fields: ["id", "campaign.*", "campaign.promotions.*"],
76
+ filters: { type: CampaignTypeEnum.FlashSale },
77
+ });
78
+
79
+ const campaigns = customCampaigns.map((campaign) => campaign.campaign);
80
+
81
+ res.status(200).json({
82
+ campaigns,
83
+ count,
84
+ limit: take,
85
+ offset: skip,
86
+ });
87
+ };
@@ -0,0 +1,32 @@
1
+ import {
2
+ defineMiddlewares,
3
+ validateAndTransformBody,
4
+ validateAndTransformQuery,
5
+ } from "@medusajs/framework";
6
+ import {
7
+ createCustomCampaignSchema,
8
+ GetFlashSalesSchema,
9
+ } from "./admin/flash-sales/route";
10
+
11
+ export default defineMiddlewares([
12
+ {
13
+ methods: ["POST"],
14
+ matcher: "/admin/flash-sales",
15
+ middlewares: [validateAndTransformBody(createCustomCampaignSchema as any)],
16
+ },
17
+ {
18
+ methods: ["PUT"],
19
+ matcher: "/admin/flash-sales/:id",
20
+ middlewares: [validateAndTransformBody(createCustomCampaignSchema as any)],
21
+ },
22
+ {
23
+ methods: ["GET"],
24
+ matcher: "/admin/flash-sales",
25
+ middlewares: [
26
+ validateAndTransformQuery(GetFlashSalesSchema as any, {
27
+ defaults: ["*", "campaign.*"],
28
+ isList: true,
29
+ }),
30
+ ],
31
+ },
32
+ ]);