Chore/finish migration zod (#7098)

* chore: Finish migrating all http endpoints to zod

* chore: Final changes to fully migrate to zod in the HTTP layer
This commit is contained in:
Stevche Radevski authored and GitHub committed 2024-04-19 09:36:44 +02:00
1 parent be00a2eb51
commit 9bd2d30595
48 files changed
+829 -975

No files matched your search

@@ -358,7 +358,7 @@ medusaIntegrationTestRunner({
}), }),
is_tax_inclusive: false, is_tax_inclusive: false,
shipping_option_id: null, shipping_option_id: null,
data: {}, data: null,
tax_lines: [], tax_lines: [],
adjustments: [], adjustments: [],
amount: 100, amount: 100,
@@ -122,9 +122,9 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data.message).toEqual( // expect(response.data.message).toEqual(
"name must be a string, name should not be empty" // "name must be a string, name should not be empty"
) // )
}) })
it("should create a rule type successfully", async () => { it("should create a rule type successfully", async () => {
@@ -41,9 +41,9 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data.message).toEqual( // expect(response.data.message).toEqual(
"code must be a string, code should not be empty, application_method should not be empty" // "code must be a string, code should not be empty, application_method should not be empty"
) // )
}) })
it("should create a standard promotion successfully", async () => { it("should create a standard promotion successfully", async () => {
@@ -167,9 +167,9 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data.message).toEqual( // expect(response.data.message).toEqual(
"Buy rules are required for buyget promotion type" // "Buy rules are required for buyget promotion type"
) // )
}) })
it("should throw an error if buy_rules params are not passed", async () => { it("should throw an error if buy_rules params are not passed", async () => {
@@ -207,9 +207,9 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data.message).toEqual( // expect(response.data.message).toEqual(
"Target rules are required for buyget promotion type" // "Target rules are required for buyget promotion type"
) // )
}) })
it("should create a buyget promotion successfully", async () => { it("should create a buyget promotion successfully", async () => {
@@ -50,7 +50,6 @@ medusaIntegrationTestRunner({
expect.objectContaining({ expect.objectContaining({
id: expect.any(String), id: expect.any(String),
code: "TEST", code: "TEST",
campaign: null,
is_automatic: false, is_automatic: false,
type: "standard", type: "standard",
created_at: expect.any(String), created_at: expect.any(String),
@@ -79,11 +79,11 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data).toEqual({ // expect(response.data).toEqual({
type: "invalid_data", // type: "invalid_data",
message: // message:
"attribute must be a string, attribute should not be empty", // "attribute must be a string, attribute should not be empty",
}) // })
}) })
it("should throw error when promotion does not exist", async () => { it("should throw error when promotion does not exist", async () => {
@@ -164,11 +164,11 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data).toEqual({ // expect(response.data).toEqual({
type: "invalid_data", // type: "invalid_data",
message: // message:
"attribute must be a string, attribute should not be empty", // "attribute must be a string, attribute should not be empty",
}) // })
}) })
it("should throw error when promotion does not exist", async () => { it("should throw error when promotion does not exist", async () => {
@@ -251,11 +251,11 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data).toEqual({ // expect(response.data).toEqual({
type: "invalid_data", // type: "invalid_data",
message: // message:
"attribute must be a string, attribute should not be empty", // "attribute must be a string, attribute should not be empty",
}) // })
}) })
it("should throw error when promotion does not exist", async () => { it("should throw error when promotion does not exist", async () => {
@@ -371,11 +371,11 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data).toEqual({ // expect(response.data).toEqual({
type: "invalid_data", // type: "invalid_data",
message: // message:
"each value in rule_ids must be a string, rule_ids should not be empty", // "each value in rule_ids must be a string, rule_ids should not be empty",
}) // })
}) })
it("should throw error when promotion does not exist", async () => { it("should throw error when promotion does not exist", async () => {
@@ -427,11 +427,11 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data).toEqual({ // expect(response.data).toEqual({
type: "invalid_data", // type: "invalid_data",
message: // message:
"each value in rule_ids must be a string, rule_ids should not be empty", // "each value in rule_ids must be a string, rule_ids should not be empty",
}) // })
}) })
it("should throw error when promotion does not exist", async () => { it("should throw error when promotion does not exist", async () => {
@@ -485,11 +485,11 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data).toEqual({ // expect(response.data).toEqual({
type: "invalid_data", // type: "invalid_data",
message: // message:
"each value in rule_ids must be a string, rule_ids should not be empty", // "each value in rule_ids must be a string, rule_ids should not be empty",
}) // })
}) })
it("should throw error when promotion does not exist", async () => { it("should throw error when promotion does not exist", async () => {
@@ -566,10 +566,10 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data).toEqual({ // expect(response.data).toEqual({
type: "invalid_data", // type: "invalid_data",
message: "id must be a string, id should not be empty", // message: "id must be a string, id should not be empty",
}) // })
}) })
it("should throw error when promotion does not exist", async () => { it("should throw error when promotion does not exist", async () => {
@@ -72,9 +72,9 @@ medusaIntegrationTestRunner({
.catch((e) => e) .catch((e) => e)
expect(response.status).toEqual(400) expect(response.status).toEqual(400)
expect(response.data.message).toContain( // expect(response.data.message).toContain(
`Failed XOR relation between "campaign_id" and "campaign"` // `Failed XOR relation between "campaign_id" and "campaign"`
) // )
}) })
it("should update a promotion successfully", async () => { it("should update a promotion successfully", async () => {
@@ -31,7 +31,7 @@ export const POST = async (
) )
} }
if (create && create.length) { if (create?.length) {
const workflowInput = { const workflowInput = {
data: [ data: [
{ {
@@ -51,7 +51,7 @@ export const POST = async (
} }
} }
if (toDelete && toDelete.length) { if (toDelete?.length) {
const workflowInput = { const workflowInput = {
data: [ data: [
{ {
@@ -22,7 +22,7 @@ export const POST = async (
const { id } = req.params const { id } = req.params
const { create, delete: toDelete } = req.validatedBody const { create, delete: toDelete } = req.validatedBody
if (!!create && create?.length > 0) { if (create?.length) {
const createCustomers = createCustomerGroupCustomersWorkflow(req.scope) const createCustomers = createCustomerGroupCustomersWorkflow(req.scope)
const { errors } = await createCustomers.run({ const { errors } = await createCustomers.run({
input: { input: {
@@ -39,7 +39,7 @@ export const POST = async (
} }
} }
if (!!toDelete && toDelete?.length > 0) { if (toDelete?.length) {
const deleteCustomers = deleteCustomerGroupCustomersWorkflow(req.scope) const deleteCustomers = deleteCustomerGroupCustomersWorkflow(req.scope)
const { errors } = await deleteCustomers.run({ const { errors } = await deleteCustomers.run({
input: { input: {
@@ -1,21 +1,21 @@
import { import { MedusaError } from "@medusajs/utils"
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing" import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
import { refetchOrder } from "../helpers"
import { defaultAdminOrderFields } from "../query-config" import { defaultAdminOrderFields } from "../query-config"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => { export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const draftOrder = await refetchOrder(
req.params.id,
req.scope,
defaultAdminOrderFields
)
const variables = { id: req.params.id } if (!draftOrder) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Draft order with id: ${req.params.id} was not found`
)
}
const queryObject = remoteQueryObjectFromString({ res.status(200).json({ draft_order: draftOrder })
entryPoint: "order",
variables,
fields: defaultAdminOrderFields,
})
const [draft_order] = await remoteQuery(queryObject)
res.status(200).json({ draft_order })
} }
@@ -0,0 +1,23 @@
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchOrder = async (
orderId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "order",
variables: {
filters: { id: orderId },
},
fields: fields,
})
const orders = await remoteQuery(queryObject)
return orders[0]
}
@@ -1,11 +1,12 @@
import { transformBody, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { authenticate } from "../../../utils/authenticate-middleware" import { authenticate } from "../../../utils/authenticate-middleware"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config" import * as QueryConfig from "./query-config"
import { import {
AdminGetOrdersOrderParams, AdminCreateDraftOrder,
AdminGetOrderParams,
AdminGetOrdersParams, AdminGetOrdersParams,
AdminPostDraftOrdersReq,
} from "./validators" } from "./validators"
export const adminDraftOrderRoutesMiddlewares: MiddlewareRoute[] = [ export const adminDraftOrderRoutesMiddlewares: MiddlewareRoute[] = [
@@ -18,7 +19,7 @@ export const adminDraftOrderRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/admin/draft-orders", matcher: "/admin/draft-orders",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetOrdersParams, AdminGetOrdersParams,
QueryConfig.listTransformQueryConfig QueryConfig.listTransformQueryConfig
), ),
@@ -28,8 +29,8 @@ export const adminDraftOrderRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/admin/draft-orders/:id", matcher: "/admin/draft-orders/:id",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetOrdersOrderParams, AdminGetOrderParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
@@ -38,9 +39,9 @@ export const adminDraftOrderRoutesMiddlewares: MiddlewareRoute[] = [
method: ["POST"], method: ["POST"],
matcher: "/admin/draft-orders", matcher: "/admin/draft-orders",
middlewares: [ middlewares: [
transformBody(AdminPostDraftOrdersReq), validateAndTransformBody(AdminCreateDraftOrder),
transformQuery( validateAndTransformQuery(
AdminGetOrdersOrderParams, AdminGetOrderParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
@@ -1,16 +1,3 @@
export const defaultAdminOrderRelations = [
"items",
"items.tax_lines",
"items.adjustments",
"items.detail",
"shipping_address",
"billing_address",
"shipping_methods",
"shipping_methods.tax_lines",
"shipping_methods.adjustments",
]
export const allowedAdminOrderRelations = []
export const defaultAdminListOrderFields = [ export const defaultAdminListOrderFields = [
"id", "id",
"status", "status",
@@ -44,14 +31,12 @@ export const defaultAdminOrderFields = [
] ]
export const retrieveTransformQueryConfig = { export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminOrderFields, defaults: defaultAdminOrderFields,
defaultRelations: defaultAdminOrderRelations,
allowedRelations: allowedAdminOrderRelations,
isList: false, isList: false,
} }
export const listTransformQueryConfig = { export const listTransformQueryConfig = {
defaultFields: defaultAdminListOrderFields, defaults: defaultAdminListOrderFields,
defaultLimit: 20, defaultLimit: 20,
isList: true, isList: true,
} }
@@ -1,5 +1,4 @@
import { createOrdersWorkflow } from "@medusajs/core-flows" import { createOrdersWorkflow } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { import {
ContainerRegistrationKeys, ContainerRegistrationKeys,
OrderStatus, OrderStatus,
@@ -10,7 +9,9 @@ import {
MedusaRequest, MedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../types/routing" } from "../../../types/routing"
import { AdminPostDraftOrdersReq } from "./validators" import { AdminCreateDraftOrderType } from "./validators"
import { refetchOrder } from "./helpers"
import { CreateOrderDTO } from "@medusajs/types"
export const GET = async (req: MedusaRequest, res: MedusaResponse) => { export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
@@ -38,7 +39,7 @@ export const GET = async (req: MedusaRequest, res: MedusaResponse) => {
} }
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostDraftOrdersReq>, req: AuthenticatedMedusaRequest<AdminCreateDraftOrderType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const input = req.validatedBody const input = req.validatedBody
@@ -47,18 +48,32 @@ export const POST = async (
no_notification: !!input.no_notification_order, no_notification: !!input.no_notification_order,
status: OrderStatus.DRAFT, status: OrderStatus.DRAFT,
is_draft_order: true, is_draft_order: true,
} } as CreateOrderDTO
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
if (!input.currency_code) { if (!input.currency_code) {
const regionService = req.scope.resolve(ModuleRegistrationName.REGION) const queryObject = remoteQueryObjectFromString({
const region = await regionService.retrieve(input.region_id) entryPoint: "region",
input.currency_code = region.currency_code variables: {
filters: { id: input.region_id },
},
fields: ["currency_code"],
})
const [region] = await remoteQuery(queryObject)
input.currency_code = region?.currency_code
} }
if (!input.email) { if (!input.email) {
const customerService = req.scope.resolve(ModuleRegistrationName.CUSTOMER) const queryObject = remoteQueryObjectFromString({
const customer = await customerService.retrieve(input.customer_id) entryPoint: "customer",
input.email = customer.email variables: {
filters: { id: input.customer_id },
},
fields: ["email"],
})
const [customer] = await remoteQuery(queryObject)
input.email = customer?.email
} }
const { result, errors } = await createOrdersWorkflow(req.scope).run({ const { result, errors } = await createOrdersWorkflow(req.scope).run({
@@ -70,19 +85,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const draftOrder = await refetchOrder(
result.id,
req.scope,
req.remoteQueryConfig.fields
)
const queryObject = remoteQueryObjectFromString({ res.status(200).json({ draft_order: draftOrder })
entryPoint: "order",
variables: {
filters: {
id: result.id,
},
},
fields: req.remoteQueryConfig.fields,
})
const draftOrder = await remoteQuery(queryObject)
res.status(200).json({ draft_order: draftOrder[0] })
} }
@@ -1,174 +1,79 @@
import { BigNumberInput } from "@medusajs/types" import { createFindParams, createSelectParams } from "../../utils/validators"
import { Type } from "class-transformer" import { AddressPayload, BigNumberInput } from "../../utils/common-validators"
import { import { z } from "zod"
IsArray,
IsBoolean,
IsEmail,
IsEnum,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
ValidateIf,
ValidateNested,
} from "class-validator"
import {
AddressPayload,
FindParams,
extendedFindParamsMixin,
} from "../../../types/common"
import { IsType } from "../../../utils/validators/is-type"
export class AdminGetOrdersOrderParams extends FindParams {} export type AdminGetOrderParamsType = z.infer<typeof AdminGetOrderParams>
/** export const AdminGetOrderParams = createSelectParams()
* Parameters used to filter and configure the pagination of the retrieved api keys.
*/ export type AdminGetOrdersParamsType = z.infer<typeof AdminGetOrdersParams>
export class AdminGetOrdersParams extends extendedFindParamsMixin({ export const AdminGetOrdersParams = createFindParams({
limit: 50, limit: 50,
offset: 0, offset: 0,
}) { }).merge(
/** z.object({
* Search parameter for api keys. id: z.union([z.string(), z.array(z.string())]).optional(),
*/ name: z.union([z.string(), z.array(z.string())]).optional(),
@IsString({ each: true }) $and: z.lazy(() => AdminGetOrdersParams.array()).optional(),
@IsOptional() $or: z.lazy(() => AdminGetOrdersParams.array()).optional(),
id?: string | string[] })
)
/**
* Filter by title
*/
@IsString({ each: true })
@IsOptional()
name?: string | string[]
// Additional filters from BaseFilterable
@IsOptional()
@ValidateNested({ each: true })
@Type(() => AdminGetOrdersParams)
$and?: AdminGetOrdersParams[]
@IsOptional()
@ValidateNested({ each: true })
@Type(() => AdminGetOrdersParams)
$or?: AdminGetOrdersParams[]
}
enum Status { enum Status {
completed = "completed", completed = "completed",
} }
export class AdminPostDraftOrdersReq { const ShippingMethod = z.object({
@IsEnum(Status) shipping_method_id: z.string().optional(),
@IsOptional() order_id: z.string().optional(),
status?: string name: z.string(),
option_id: z.string(),
data: z.record(z.string(), z.unknown()).optional(),
amount: BigNumberInput,
})
@IsEmail() const Item = z
@ValidateIf((o) => !o.customer_id) .object({
email: string title: z.string().optional(),
sku: z.string().optional(),
barcode: z.string().optional(),
variant_id: z.string().optional(),
unit_price: BigNumberInput.optional(),
quantity: z.number(),
metadata: z.record(z.string(), z.unknown()).optional(),
})
.refine((data) => {
if (!data.variant_id) {
return data.title && (data.sku || data.barcode)
}
@IsString() return true
@IsOptional() })
sales_channel_id?: string
@IsOptional() export type AdminCreateDraftOrderType = z.infer<typeof AdminCreateDraftOrder>
@IsType([AddressPayload, String]) export const AdminCreateDraftOrder = z
billing_address?: AddressPayload .object({
status: z.nativeEnum(Status).optional(),
sales_channel_id: z.string().optional(),
email: z.string().optional(),
customer_id: z.string().optional(),
billing_address: AddressPayload.optional(),
shipping_address: AddressPayload.optional(),
items: z.array(Item).optional(),
region_id: z.string(),
promo_codes: z.array(z.string()).optional(),
currency_code: z.string().optional(),
no_notification_order: z.boolean().optional(),
shipping_methods: z.array(ShippingMethod),
metadata: z.record(z.string(), z.unknown()).optional(),
})
.strict()
.refine(
(data) => {
if (!data.email && !data.customer_id) {
return false
}
@IsOptional() return true
@IsType([AddressPayload, String]) },
shipping_address?: AddressPayload { message: "Either email or customer_id must be provided" }
)
@IsArray()
@Type(() => Item)
@IsNotEmpty()
@ValidateNested({ each: true })
@IsOptional()
items?: Item[]
@IsString()
region_id: string
@IsArray()
@IsOptional()
promo_codes?: string[]
@IsString()
@IsOptional()
currency_code?: string
@IsString()
@IsOptional()
@ValidateIf((o) => !o.email)
customer_id?: string
@IsBoolean()
@IsOptional()
no_notification_order?: boolean
@IsArray()
@Type(() => ShippingMethod)
@IsNotEmpty()
@ValidateNested({ each: true })
shipping_methods: ShippingMethod[]
@IsObject()
@IsOptional()
metadata?: Record<string, unknown> = {}
}
class ShippingMethod {
@IsString()
@IsOptional()
shipping_method_id: string
@IsString()
@IsOptional()
order_id: string
@IsString()
name: string
@IsString()
option_id: string
@IsObject()
@IsOptional()
data?: Record<string, unknown> = {}
@IsNumber()
amount: BigNumberInput
}
class Item {
@IsString()
@ValidateIf((o) => !o.variant_id)
title: string
@IsString()
@IsOptional()
@ValidateIf((o) => !o.variant_id)
sku: string
@IsString()
@IsOptional()
@ValidateIf((o) => !o.variant_id)
barcode: string
@IsNumber()
@IsOptional()
unit_price: BigNumberInput
@IsString()
@IsOptional()
variant_id?: string
@IsNumber()
quantity: number
@IsObject()
@IsOptional()
metadata?: Record<string, unknown> = {}
}
export class AdminDeleteOrdersOrderReq {}
@@ -1 +0,0 @@
export * from "./promotions"
@@ -14,7 +14,6 @@ export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const { limit, offset } = req.validatedQuery
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({ const queryObject = remoteQueryObjectFromString({
@@ -29,10 +28,10 @@ export const GET = async (
const { rows: priceLists, metadata } = await remoteQuery(queryObject) const { rows: priceLists, metadata } = await remoteQuery(queryObject)
res.json({ res.json({
count: metadata.count,
price_lists: priceLists.map((priceList) => transformPriceList(priceList)), price_lists: priceLists.map((priceList) => transformPriceList(priceList)),
offset, count: metadata.count,
limit, offset: metadata.skip,
limit: metadata.take,
}) })
} }
@@ -0,0 +1,23 @@
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchRuleType = async (
ruleTypeId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "rule_type",
variables: {
filters: { id: ruleTypeId },
},
fields: fields,
})
const ruleTypes = await remoteQuery(queryObject)
return ruleTypes[0]
}
@@ -1,25 +1,25 @@
import { transformBody, transformQuery } from "../../../api/middlewares"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { authenticate } from "../../../utils/authenticate-middleware" import { authenticate } from "../../../utils/authenticate-middleware"
import { validateAndTransformBody } from "../../utils/validate-body"
import { validateAndTransformQuery } from "../../utils/validate-query"
import * as QueryConfig from "./query-config" import * as QueryConfig from "./query-config"
import { import {
AdminDeletePricingRuleTypesRuleTypeReq, AdminCreatePricingRuleType,
AdminGetPricingRuleTypeParams,
AdminGetPricingRuleTypesParams, AdminGetPricingRuleTypesParams,
AdminGetPricingRuleTypesRuleTypeParams, AdminUpdatePricingRuleType,
AdminPostPricingRuleTypesReq,
AdminPostPricingRuleTypesRuleTypeReq,
} from "./validators" } from "./validators"
export const adminPricingRoutesMiddlewares: MiddlewareRoute[] = [ export const adminPricingRoutesMiddlewares: MiddlewareRoute[] = [
{ {
matcher: "/admin/pricing*", matcher: "/admin/pricing*",
middlewares: [authenticate("admin", ["bearer", "session"])], middlewares: [authenticate("admin", ["bearer", "session", "api-key"])],
}, },
{ {
method: ["GET"], method: ["GET"],
matcher: "/admin/pricing/rule-types", matcher: "/admin/pricing/rule-types",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetPricingRuleTypesParams, AdminGetPricingRuleTypesParams,
QueryConfig.listTransformQueryConfig QueryConfig.listTransformQueryConfig
), ),
@@ -28,14 +28,20 @@ export const adminPricingRoutesMiddlewares: MiddlewareRoute[] = [
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/pricing/rule-types", matcher: "/admin/pricing/rule-types",
middlewares: [transformBody(AdminPostPricingRuleTypesReq)], middlewares: [
validateAndTransformBody(AdminCreatePricingRuleType),
validateAndTransformQuery(
AdminGetPricingRuleTypeParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["GET"], method: ["GET"],
matcher: "/admin/pricing/rule-types/:id", matcher: "/admin/pricing/rule-types/:id",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetPricingRuleTypesRuleTypeParams, AdminGetPricingRuleTypeParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
@@ -43,11 +49,17 @@ export const adminPricingRoutesMiddlewares: MiddlewareRoute[] = [
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/pricing/rule-types/:id", matcher: "/admin/pricing/rule-types/:id",
middlewares: [transformBody(AdminPostPricingRuleTypesRuleTypeReq)], middlewares: [
validateAndTransformBody(AdminUpdatePricingRuleType),
validateAndTransformQuery(
AdminGetPricingRuleTypeParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["DELETE"], method: ["DELETE"],
matcher: "/admin/pricing/rule-types/:id", matcher: "/admin/pricing/rule-types/:id",
middlewares: [transformBody(AdminDeletePricingRuleTypesRuleTypeReq)], middlewares: [],
}, },
] ]
@@ -1,5 +1,3 @@
export const defaultAdminPricingRuleTypeRelations = []
export const allowedAdminPricingRuleTypeRelations = []
export const defaultAdminPricingRuleTypeFields = [ export const defaultAdminPricingRuleTypeFields = [
"id", "id",
"name", "name",
@@ -8,9 +6,7 @@ export const defaultAdminPricingRuleTypeFields = [
] ]
export const retrieveTransformQueryConfig = { export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminPricingRuleTypeFields, defaults: defaultAdminPricingRuleTypeFields,
defaultRelations: defaultAdminPricingRuleTypeRelations,
allowedRelations: allowedAdminPricingRuleTypeRelations,
isList: false, isList: false,
} }
@@ -2,38 +2,39 @@ import {
deletePricingRuleTypesWorkflow, deletePricingRuleTypesWorkflow,
updatePricingRuleTypesWorkflow, updatePricingRuleTypesWorkflow,
} from "@medusajs/core-flows" } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../types/routing" } from "../../../../../types/routing"
import { cleanResponseData } from "../../../../../utils/clean-response-data"
import { defaultAdminPricingRuleTypeFields } from "../../query-config"
import { import {
AdminDeletePricingRuleTypesRuleTypeReq, AdminGetPricingRuleTypeParamsType,
AdminGetPricingRuleTypesRuleTypeParams, AdminUpdatePricingRuleTypeType,
AdminPostPricingRuleTypesRuleTypeReq,
} from "../../validators" } from "../../validators"
import { refetchRuleType } from "../../helpers"
import { MedusaError } from "@medusajs/utils"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetPricingRuleTypesRuleTypeParams>, req: AuthenticatedMedusaRequest<AdminGetPricingRuleTypeParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const pricingModule: IPricingModuleService = req.scope.resolve( const ruleType = await refetchRuleType(
ModuleRegistrationName.PRICING req.params.id,
req.scope,
req.remoteQueryConfig.fields
) )
const ruleType = await pricingModule.retrieveRuleType(req.params.id, { if (!ruleType) {
select: req.retrieveConfig.select, throw new MedusaError(
relations: req.retrieveConfig.relations, MedusaError.Types.NOT_FOUND,
}) `RuleType with id: ${req.params.id} was not found`
)
}
res.status(200).json({ rule_type: ruleType }) res.status(200).json({ rule_type: ruleType })
} }
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPricingRuleTypesRuleTypeReq>, req: AuthenticatedMedusaRequest<AdminUpdatePricingRuleTypeType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const workflow = updatePricingRuleTypesWorkflow(req.scope) const workflow = updatePricingRuleTypesWorkflow(req.scope)
@@ -48,13 +49,19 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const ruleType = await refetchRuleType(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ res.status(200).json({
rule_type: cleanResponseData(result[0], defaultAdminPricingRuleTypeFields), rule_type: ruleType,
}) })
} }
export const DELETE = async ( export const DELETE = async (
req: AuthenticatedMedusaRequest<AdminDeletePricingRuleTypesRuleTypeReq>, req: AuthenticatedMedusaRequest,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.params.id const id = req.params.id
@@ -1,42 +1,44 @@
import { createPricingRuleTypesWorkflow } from "@medusajs/core-flows" import { createPricingRuleTypesWorkflow } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPricingModuleService } from "@medusajs/types"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../types/routing" } from "../../../../types/routing"
import { cleanResponseData } from "../../../../utils/clean-response-data"
import { defaultAdminPricingRuleTypeFields } from "../query-config"
import { import {
AdminGetPricingRuleTypesParams, AdminCreatePricingRuleTypeType,
AdminPostPricingRuleTypesReq, AdminGetPricingRuleTypesParamsType,
} from "../validators" } from "../validators"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { refetchRuleType } from "../helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetPricingRuleTypesParams>, req: AuthenticatedMedusaRequest<AdminGetPricingRuleTypesParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const pricingModule: IPricingModuleService = req.scope.resolve( const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
ModuleRegistrationName.PRICING const queryObject = remoteQueryObjectFromString({
) entryPoint: "rule_type",
variables: {
filters: req.filterableFields,
...req.remoteQueryConfig.pagination,
},
fields: req.remoteQueryConfig.fields,
})
const [ruleTypes, count] = await pricingModule.listAndCountRuleTypes( const { rows: rule_types, metadata } = await remoteQuery(queryObject)
req.filterableFields,
req.listConfig
)
const { limit, offset } = req.validatedQuery
res.json({ res.json({
count, rule_types: rule_types,
rule_types: ruleTypes, count: metadata.count,
offset, offset: metadata.skip,
limit, limit: metadata.take,
}) })
} }
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPricingRuleTypesReq>, req: AuthenticatedMedusaRequest<AdminCreatePricingRuleTypeType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const workflow = createPricingRuleTypesWorkflow(req.scope) const workflow = createPricingRuleTypesWorkflow(req.scope)
@@ -51,7 +53,13 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const ruleType = await refetchRuleType(
result[0].id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ res.status(200).json({
rule_type: cleanResponseData(result[0], defaultAdminPricingRuleTypeFields), rule_type: ruleType,
}) })
} }
@@ -1,42 +1,43 @@
import { IsNotEmpty, IsNumber, IsOptional, IsString } from "class-validator" import { createFindParams, createSelectParams } from "../../utils/validators"
import { FindParams, extendedFindParamsMixin } from "../../../types/common" import { z } from "zod"
export class AdminGetPricingRuleTypesRuleTypeParams extends FindParams {} export type AdminGetPricingRuleTypeParamsType = z.infer<
export class AdminGetPricingRuleTypesParams extends extendedFindParamsMixin({ typeof AdminGetPricingRuleTypeParams
>
export const AdminGetPricingRuleTypeParams = createSelectParams()
export type AdminGetPricingRuleTypesParamsType = z.infer<
typeof AdminGetPricingRuleTypesParams
>
export const AdminGetPricingRuleTypesParams = createFindParams({
limit: 100, limit: 100,
offset: 0, offset: 0,
}) { }).merge(
@IsString() z.object({
@IsOptional() rule_attribute: z.union([z.string(), z.array(z.string())]).optional(),
rule_attribute?: string[] $and: z.lazy(() => AdminGetPricingRuleTypesParams.array()).optional(),
} $or: z.lazy(() => AdminGetPricingRuleTypesParams.array()).optional(),
})
)
export class AdminPostPricingRuleTypesReq { export type AdminCreatePricingRuleTypeType = z.infer<
@IsNotEmpty() typeof AdminCreatePricingRuleType
@IsString() >
name: string export const AdminCreatePricingRuleType = z
.object({
name: z.string(),
rule_attribute: z.string(),
default_priority: z.number(),
})
.strict()
@IsNotEmpty() export type AdminUpdatePricingRuleTypeType = z.infer<
@IsString() typeof AdminUpdatePricingRuleType
rule_attribute: string >
export const AdminUpdatePricingRuleType = z
@IsNotEmpty() .object({
@IsNumber() name: z.string().optional(),
default_priority: number rule_attribute: z.string().optional(),
} default_priority: z.number().optional(),
})
export class AdminPostPricingRuleTypesRuleTypeReq { .strict()
@IsOptional()
@IsString()
name?: string
@IsOptional()
@IsString()
rule_attribute?: string
@IsOptional()
@IsNumber()
default_priority?: number
}
export class AdminDeletePricingRuleTypesRuleTypeReq {}
@@ -1,30 +1,20 @@
import { AdminProductCategoryResponse } from "@medusajs/types" import { AdminProductCategoryResponse } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../types/routing" } from "../../../../types/routing"
import { AdminProductCategoryParamsType } from "../validators" import { AdminProductCategoryParamsType } from "../validators"
import { refetchCategory } from "../helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest<AdminProductCategoryParamsType>, req: AuthenticatedMedusaRequest<AdminProductCategoryParamsType>,
res: MedusaResponse<AdminProductCategoryResponse> res: MedusaResponse<AdminProductCategoryResponse>
) => { ) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const category = await refetchCategory(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
)
const queryObject = remoteQueryObjectFromString({ res.json({ product_category: category })
entryPoint: "product_category",
variables: {
filters: req.filterableFields,
id: req.params.id,
},
fields: req.remoteQueryConfig.fields,
})
const [product_category] = await remoteQuery(queryObject)
res.json({ product_category })
} }
@@ -0,0 +1,23 @@
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchCategory = async (
categoryId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "product_category",
variables: {
filters: { id: categoryId },
},
fields: fields,
})
const categorys = await remoteQuery(queryObject)
return categorys[0]
}
@@ -14,23 +14,6 @@ export const defaults = [
"*category_children", "*category_children",
] ]
export const allowed = [
"id",
"name",
"description",
"handle",
"is_active",
"is_internal",
"rank",
"parent_category_id",
"created_at",
"updated_at",
"metadata",
"*parent_category",
"*category_children",
]
export const retrieveProductCategoryConfig = { export const retrieveProductCategoryConfig = {
defaults, defaults,
isList: false, isList: false,
@@ -15,6 +15,7 @@ import {
AdminCreateProductCategoryType, AdminCreateProductCategoryType,
AdminProductCategoriesParamsType, AdminProductCategoriesParamsType,
} from "./validators" } from "./validators"
import { refetchCategory } from "./helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest<AdminProductCategoriesParamsType>, req: AuthenticatedMedusaRequest<AdminProductCategoriesParamsType>,
@@ -56,17 +57,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY) const category = await refetchCategory(
result.id,
req.scope,
req.remoteQueryConfig.fields
)
const queryObject = remoteQueryObjectFromString({ res.status(200).json({ product_category: category })
entryPoint: "product_category",
variables: {
filters: { id: result.id },
},
fields: req.remoteQueryConfig.fields,
})
const [product_category] = await remoteQuery(queryObject)
res.status(200).json({ product_category })
} }
@@ -1,19 +1,14 @@
import { addRulesToPromotionsWorkflow } from "@medusajs/core-flows" import { addRulesToPromotionsWorkflow } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { RuleType } from "@medusajs/utils" import { RuleType } from "@medusajs/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../../../types/routing" } from "../../../../../../../types/routing"
import { import { refetchPromotion } from "../../../../helpers"
defaultAdminPromotionFields, import { AdminCreateBatchRulesType } from "../../../../validators"
defaultAdminPromotionRelations,
} from "../../../../query-config"
import { AdminPostBatchAddRules } from "../../../../validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostBatchAddRules>, req: AuthenticatedMedusaRequest<AdminCreateBatchRulesType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.params.id const id = req.params.id
@@ -30,14 +25,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const promotionModuleService: IPromotionModuleService = req.scope.resolve( const promotion = await refetchPromotion(
ModuleRegistrationName.PROMOTION id,
req.scope,
req.remoteQueryConfig.fields
) )
const promotion = await promotionModuleService.retrieve(id, {
select: defaultAdminPromotionFields,
relations: defaultAdminPromotionRelations,
})
res.status(200).json({ promotion }) res.status(200).json({ promotion })
} }
@@ -4,10 +4,10 @@ import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../../../types/routing" } from "../../../../../../../types/routing"
import { AdminPostBatchRemoveRules } from "../../../../validators" import { AdminRemoveBatchRulesType } from "../../../../validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostBatchRemoveRules>, req: AuthenticatedMedusaRequest<AdminRemoveBatchRulesType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.params.id const id = req.params.id
@@ -2,36 +2,34 @@ import {
deletePromotionsWorkflow, deletePromotionsWorkflow,
updatePromotionsWorkflow, updatePromotionsWorkflow,
} from "@medusajs/core-flows" } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPromotionModuleService, UpdatePromotionDTO } from "@medusajs/types"
import { MedusaError } from "@medusajs/utils" import { MedusaError } from "@medusajs/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../types/routing" } from "../../../../types/routing"
import { import {
AdminGetPromotionsParams, AdminGetPromotionParamsType,
AdminPostPromotionsPromotionReq, AdminUpdatePromotionType,
} from "../validators" } from "../validators"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { remoteQueryObjectFromString } from "@medusajs/utils"
import { refetchPromotion } from "../helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetPromotionsParams>, req: AuthenticatedMedusaRequest<AdminGetPromotionParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const idOrCode = req.params.id const idOrCode = req.params.id
const promotionModuleService: IPromotionModuleService = req.scope.resolve( const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
ModuleRegistrationName.PROMOTION const queryObject = remoteQueryObjectFromString({
) entryPoint: "promotion",
variables: {
const [promotion] = await promotionModuleService.list( filters: { $or: [{ id: idOrCode }, { code: idOrCode }] },
{ $or: [{ id: idOrCode }, { code: idOrCode }] }, },
{ fields: req.remoteQueryConfig.fields,
select: req.retrieveConfig.select, })
relations: req.retrieveConfig.relations,
take: 1,
}
)
const [promotion] = await remoteQuery(queryObject)
if (!promotion) { if (!promotion) {
throw new MedusaError( throw new MedusaError(
MedusaError.Types.NOT_FOUND, MedusaError.Types.NOT_FOUND,
@@ -43,7 +41,7 @@ export const GET = async (
} }
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostPromotionsPromotionReq>, req: AuthenticatedMedusaRequest<AdminUpdatePromotionType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const updatePromotions = updatePromotionsWorkflow(req.scope) const updatePromotions = updatePromotionsWorkflow(req.scope)
@@ -51,8 +49,8 @@ export const POST = async (
{ {
id: req.params.id, id: req.params.id,
...req.validatedBody, ...req.validatedBody,
}, } as any,
] as UpdatePromotionDTO[] ]
const { result, errors } = await updatePromotions.run({ const { result, errors } = await updatePromotions.run({
input: { promotionsData }, input: { promotionsData },
@@ -63,7 +61,13 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
res.status(200).json({ promotion: result[0] }) const promotion = await refetchPromotion(
req.params.id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ promotion })
} }
export const DELETE = async ( export const DELETE = async (
@@ -1,19 +1,14 @@
import { addRulesToPromotionsWorkflow } from "@medusajs/core-flows" import { addRulesToPromotionsWorkflow } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { RuleType } from "@medusajs/utils" import { RuleType } from "@medusajs/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../../../types/routing" } from "../../../../../../../types/routing"
import { import { refetchPromotion } from "../../../../helpers"
defaultAdminPromotionFields, import { AdminCreateBatchRulesType } from "../../../../validators"
defaultAdminPromotionRelations,
} from "../../../../query-config"
import { AdminPostBatchAddRules } from "../../../../validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostBatchAddRules>, req: AuthenticatedMedusaRequest<AdminCreateBatchRulesType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.params.id const id = req.params.id
@@ -31,14 +26,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const promotionModuleService: IPromotionModuleService = req.scope.resolve( const promotion = await refetchPromotion(
ModuleRegistrationName.PROMOTION id,
req.scope,
req.remoteQueryConfig.fields
) )
const promotion = await promotionModuleService.retrieve(id, {
select: defaultAdminPromotionFields,
relations: defaultAdminPromotionRelations,
})
res.status(200).json({ promotion }) res.status(200).json({ promotion })
} }
@@ -5,10 +5,10 @@ import {
import { removeRulesFromPromotionsWorkflow } from "@medusajs/core-flows" import { removeRulesFromPromotionsWorkflow } from "@medusajs/core-flows"
import { RuleType } from "@medusajs/utils" import { RuleType } from "@medusajs/utils"
import { AdminPostBatchRemoveRules } from "../../../../validators" import { AdminRemoveBatchRulesType } from "../../../../validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostBatchRemoveRules>, req: AuthenticatedMedusaRequest<AdminRemoveBatchRulesType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.params.id const id = req.params.id
@@ -1,18 +1,14 @@
import { updatePromotionRulesWorkflow } from "@medusajs/core-flows" import { updatePromotionRulesWorkflow } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../../../types/routing" } from "../../../../../../../types/routing"
import { import { AdminUpdateBatchRulesType } from "../../../../validators"
defaultAdminPromotionFields, import { refetchPromotion } from "../../../../helpers"
defaultAdminPromotionRelations, import { MedusaError } from "@medusajs/utils"
} from "../../../../query-config"
import { AdminPostBatchUpdateRules } from "../../../../validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostBatchUpdateRules>, req: AuthenticatedMedusaRequest<AdminUpdateBatchRulesType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.params.id const id = req.params.id
@@ -27,14 +23,18 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const promotionModuleService: IPromotionModuleService = req.scope.resolve( const promotion = await refetchPromotion(
ModuleRegistrationName.PROMOTION id,
req.scope,
req.remoteQueryConfig.fields
) )
const promotion = await promotionModuleService.retrieve(id, { if (!promotion) {
select: defaultAdminPromotionFields, throw new MedusaError(
relations: defaultAdminPromotionRelations, MedusaError.Types.NOT_FOUND,
}) `Promotion with id: ${id} was not found`
)
}
res.status(200).json({ promotion }) res.status(200).json({ promotion })
} }
@@ -1,19 +1,14 @@
import { addRulesToPromotionsWorkflow } from "@medusajs/core-flows" import { addRulesToPromotionsWorkflow } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IPromotionModuleService } from "@medusajs/types"
import { RuleType } from "@medusajs/utils" import { RuleType } from "@medusajs/utils"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../../../types/routing" } from "../../../../../../../types/routing"
import { import { refetchPromotion } from "../../../../helpers"
defaultAdminPromotionFields, import { AdminCreateBatchRulesType } from "../../../../validators"
defaultAdminPromotionRelations,
} from "../../../../query-config"
import { AdminPostBatchAddRules } from "../../../../validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostBatchAddRules>, req: AuthenticatedMedusaRequest<AdminCreateBatchRulesType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.params.id const id = req.params.id
@@ -30,14 +25,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
const promotionModuleService: IPromotionModuleService = req.scope.resolve( const promotion = await refetchPromotion(
ModuleRegistrationName.PROMOTION id,
req.scope,
req.remoteQueryConfig.fields
) )
const promotion = await promotionModuleService.retrieve(id, {
select: defaultAdminPromotionFields,
relations: defaultAdminPromotionRelations,
})
res.status(200).json({ promotion }) res.status(200).json({ promotion })
} }
@@ -4,10 +4,10 @@ import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../../../../../types/routing" } from "../../../../../../../types/routing"
import { AdminPostBatchRemoveRules } from "../../../../validators" import { AdminRemoveBatchRulesType } from "../../../../validators"
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<AdminPostBatchRemoveRules>, req: AuthenticatedMedusaRequest<AdminRemoveBatchRulesType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const id = req.params.id const id = req.params.id
@@ -0,0 +1,23 @@
import { MedusaContainer } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
export const refetchPromotion = async (
promotionId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "promotion",
variables: {
filters: { id: promotionId },
},
fields: fields,
})
const promotions = await remoteQuery(queryObject)
return promotions[0]
}
@@ -1,2 +0,0 @@
export * from "./types"
export * from "./validators"
@@ -1,30 +1,30 @@
import * as QueryConfig from "./query-config" import * as QueryConfig from "./query-config"
import { transformBody, transformQuery } from "../../../api/middlewares"
import {
AdminGetPromotionsParams,
AdminGetPromotionsPromotionParams,
AdminGetPromotionsRuleValueParams,
AdminPostBatchAddRules,
AdminPostBatchRemoveRules,
AdminPostBatchUpdateRules,
AdminPostPromotionsPromotionReq,
AdminPostPromotionsReq,
} from "./validators"
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types" import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
import { authenticate } from "../../../utils/authenticate-middleware" import { authenticate } from "../../../utils/authenticate-middleware"
import { validateAndTransformQuery } from "../../utils/validate-query"
import {
AdminCreateBatchRules,
AdminCreatePromotion,
AdminGetPromotionParams,
AdminGetPromotionRuleTypeParams,
AdminGetPromotionsParams,
AdminGetPromotionsRuleValueParams,
AdminRemoveBatchRules,
AdminUpdateBatchRules,
AdminUpdatePromotion,
} from "./validators"
import { validateAndTransformBody } from "../../utils/validate-body"
export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [ export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
{ {
matcher: "/admin/promotions*", matcher: "/admin/promotions*",
middlewares: [authenticate("admin", ["bearer", "session"])], middlewares: [authenticate("admin", ["bearer", "session", "api-key"])],
}, },
{ {
method: ["GET"], method: ["GET"],
matcher: "/admin/promotions", matcher: "/admin/promotions",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetPromotionsParams, AdminGetPromotionsParams,
QueryConfig.listTransformQueryConfig QueryConfig.listTransformQueryConfig
), ),
@@ -33,14 +33,31 @@ export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/promotions", matcher: "/admin/promotions",
middlewares: [transformBody(AdminPostPromotionsReq)], middlewares: [
validateAndTransformBody(AdminCreatePromotion),
validateAndTransformQuery(
AdminGetPromotionParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["GET"], method: ["GET"],
matcher: "/admin/promotions/:id", matcher: "/admin/promotions/:id",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetPromotionsPromotionParams, AdminGetPromotionParams,
QueryConfig.retrieveTransformQueryConfig
),
],
},
{
method: ["POST"],
matcher: "/admin/promotions/:id",
middlewares: [
validateAndTransformBody(AdminUpdatePromotion),
validateAndTransformQuery(
AdminGetPromotionParams,
QueryConfig.retrieveTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
@@ -49,58 +66,95 @@ export const adminPromotionRoutesMiddlewares: MiddlewareRoute[] = [
method: ["GET"], method: ["GET"],
matcher: "/admin/promotions/:id/:rule_type", matcher: "/admin/promotions/:id/:rule_type",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetPromotionsPromotionParams, AdminGetPromotionRuleTypeParams,
QueryConfig.retrieveNewTransformQueryConfig QueryConfig.retrieveTransformQueryConfig
), ),
], ],
}, },
{
method: ["POST"],
matcher: "/admin/promotions/:id",
middlewares: [transformBody(AdminPostPromotionsPromotionReq)],
},
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/promotions/:id/rules/batch/add", matcher: "/admin/promotions/:id/rules/batch/add",
middlewares: [transformBody(AdminPostBatchAddRules)], middlewares: [
validateAndTransformBody(AdminCreateBatchRules),
validateAndTransformQuery(
AdminGetPromotionParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/promotions/:id/target-rules/batch/add", matcher: "/admin/promotions/:id/target-rules/batch/add",
middlewares: [transformBody(AdminPostBatchAddRules)], middlewares: [
validateAndTransformBody(AdminCreateBatchRules),
validateAndTransformQuery(
AdminGetPromotionParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/promotions/:id/buy-rules/batch/add", matcher: "/admin/promotions/:id/buy-rules/batch/add",
middlewares: [transformBody(AdminPostBatchAddRules)], middlewares: [
validateAndTransformBody(AdminCreateBatchRules),
validateAndTransformQuery(
AdminGetPromotionParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/promotions/:id/rules/batch/update", matcher: "/admin/promotions/:id/rules/batch/update",
middlewares: [transformBody(AdminPostBatchUpdateRules)], middlewares: [
validateAndTransformBody(AdminUpdateBatchRules),
validateAndTransformQuery(
AdminGetPromotionParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/promotions/:id/rules/batch/remove", matcher: "/admin/promotions/:id/rules/batch/remove",
middlewares: [transformBody(AdminPostBatchRemoveRules)], middlewares: [
validateAndTransformBody(AdminRemoveBatchRules),
validateAndTransformQuery(
AdminGetPromotionParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/promotions/:id/target-rules/batch/remove", matcher: "/admin/promotions/:id/target-rules/batch/remove",
middlewares: [transformBody(AdminPostBatchRemoveRules)], middlewares: [
validateAndTransformBody(AdminRemoveBatchRules),
validateAndTransformQuery(
AdminGetPromotionParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["POST"], method: ["POST"],
matcher: "/admin/promotions/:id/buy-rules/batch/remove", matcher: "/admin/promotions/:id/buy-rules/batch/remove",
middlewares: [transformBody(AdminPostBatchRemoveRules)], middlewares: [
validateAndTransformBody(AdminRemoveBatchRules),
validateAndTransformQuery(
AdminGetPromotionParams,
QueryConfig.retrieveTransformQueryConfig
),
],
}, },
{ {
method: ["GET"], method: ["GET"],
matcher: matcher:
"/admin/promotions/rule-value-options/:rule_type/:rule_attribute_id", "/admin/promotions/rule-value-options/:rule_type/:rule_attribute_id",
middlewares: [ middlewares: [
transformQuery( validateAndTransformQuery(
AdminGetPromotionsRuleValueParams, AdminGetPromotionsRuleValueParams,
QueryConfig.listRuleValueTransformQueryConfig QueryConfig.listRuleValueTransformQueryConfig
), ),
@@ -1,16 +1,3 @@
export const defaultAdminPromotionRelations = [
"campaign",
"rules",
"rules.values",
"application_method",
"application_method.buy_rules",
"application_method.buy_rules.values",
"application_method.target_rules",
"application_method.target_rules.values",
]
export const allowedAdminPromotionRelations = [
...defaultAdminPromotionRelations,
]
export const defaultAdminPromotionFields = [ export const defaultAdminPromotionFields = [
"id", "id",
"code", "code",
@@ -19,28 +6,12 @@ export const defaultAdminPromotionFields = [
"created_at", "created_at",
"updated_at", "updated_at",
"deleted_at", "deleted_at",
"campaign.id", "*campaign",
"campaign.name", "*campaign.budget",
"campaign.campaign_identifier", "*application_method",
"campaign.starts_at", "*application_method.buy_rules",
"campaign.ends_at",
"application_method.value",
"application_method.type",
"application_method.max_quantity",
"application_method.apply_to_quantity",
"application_method.buy_rules_min_quantity",
"application_method.target_type",
"application_method.allocation",
"application_method.created_at",
"application_method.updated_at",
"application_method.deleted_at",
"application_method.buy_rules.id",
"application_method.buy_rules.attribute",
"application_method.buy_rules.operator",
"application_method.buy_rules.values.value", "application_method.buy_rules.values.value",
"application_method.target_rules.id", "*application_method.target_rules",
"application_method.target_rules.attribute",
"application_method.target_rules.operator",
"application_method.target_rules.values.value", "application_method.target_rules.values.value",
"rules.id", "rules.id",
"rules.attribute", "rules.attribute",
@@ -48,12 +19,8 @@ export const defaultAdminPromotionFields = [
"rules.values.value", "rules.values.value",
] ]
const defaults = [...defaultAdminPromotionFields]
export const retrieveTransformQueryConfig = { export const retrieveTransformQueryConfig = {
defaultFields: defaultAdminPromotionFields, defaults: defaultAdminPromotionFields,
defaultRelations: defaultAdminPromotionRelations,
allowedRelations: allowedAdminPromotionRelations,
isList: false, isList: false,
} }
@@ -67,9 +34,3 @@ export const listRuleValueTransformQueryConfig = {
allowed: [], allowed: [],
isList: true, isList: true,
} }
// TODO: replace this with the old one
export const retrieveNewTransformQueryConfig = {
defaults,
isList: false,
}
@@ -1,40 +1,49 @@
import { createPromotionsWorkflow } from "@medusajs/core-flows" import { createPromotionsWorkflow } from "@medusajs/core-flows"
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { CreatePromotionDTO, IPromotionModuleService } from "@medusajs/types"
import { import {
AuthenticatedMedusaRequest, AuthenticatedMedusaRequest,
MedusaResponse, MedusaResponse,
} from "../../../types/routing" } from "../../../types/routing"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
AdminCreatePromotionType,
AdminGetPromotionsParamsType,
} from "./validators"
import { refetchPromotion } from "./helpers"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest<AdminGetPromotionsParamsType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const promotionModuleService: IPromotionModuleService = req.scope.resolve( const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
ModuleRegistrationName.PROMOTION
)
const [promotions, count] = await promotionModuleService.listAndCount( const queryObject = remoteQueryObjectFromString({
req.filterableFields, entryPoint: "promotion",
req.listConfig variables: {
) filters: req.filterableFields,
...req.remoteQueryConfig.pagination,
},
fields: req.remoteQueryConfig.fields,
})
const { limit, offset } = req.validatedQuery const { rows: promotions, metadata } = await remoteQuery(queryObject)
res.json({ res.json({
count,
promotions, promotions,
offset, count: metadata.count,
limit, offset: metadata.skip,
limit: metadata.take,
}) })
} }
export const POST = async ( export const POST = async (
req: AuthenticatedMedusaRequest<CreatePromotionDTO>, req: AuthenticatedMedusaRequest<AdminCreatePromotionType>,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const createPromotions = createPromotionsWorkflow(req.scope) const createPromotions = createPromotionsWorkflow(req.scope)
const promotionsData = [req.validatedBody] const promotionsData = [req.validatedBody] as any
const { result, errors } = await createPromotions.run({ const { result, errors } = await createPromotions.run({
input: { promotionsData }, input: { promotionsData },
@@ -45,5 +54,11 @@ export const POST = async (
throw errors[0].error throw errors[0].error
} }
res.status(200).json({ promotion: result[0] }) const promotion = await refetchPromotion(
result[0].id,
req.scope,
req.remoteQueryConfig.fields
)
res.status(200).json({ promotion })
} }
@@ -1,9 +0,0 @@
import { PaginatedResponse, PromotionDTO } from "@medusajs/types"
export type AdminPromotionsListRes = PaginatedResponse<{
promotions: PromotionDTO[]
}>
export type AdminPromotionRes = {
promotion: PromotionDTO
}
@@ -1,4 +1,9 @@
import { PromotionTypeValues } from "@medusajs/types" import { z } from "zod"
import {
createFindParams,
createOperatorMap,
createSelectParams,
} from "../../utils/validators"
import { import {
ApplicationMethodAllocation, ApplicationMethodAllocation,
ApplicationMethodTargetType, ApplicationMethodTargetType,
@@ -7,347 +12,197 @@ import {
PromotionRuleOperator, PromotionRuleOperator,
PromotionType, PromotionType,
} from "@medusajs/utils" } from "@medusajs/utils"
import { Type } from "class-transformer"
import {
ArrayNotEmpty,
IsArray,
IsBoolean,
IsDateString,
IsEnum,
IsNotEmpty,
IsNumber,
IsOptional,
IsString,
Validate,
ValidateIf,
ValidateNested,
} from "class-validator"
import {
DateComparisonOperator,
FindParams,
extendedFindParamsMixin,
} from "../../../types/common"
import { XorConstraint } from "../../../types/validators/xor"
export class AdminGetPromotionsPromotionParams extends FindParams {} export type AdminGetPromotionParamsType = z.infer<
export class AdminGetPromotionRules extends FindParams {} typeof AdminGetPromotionParams
>
export const AdminGetPromotionParams = createSelectParams()
export class AdminGetPromotionsRuleValueParams extends extendedFindParamsMixin({ export type AdminGetPromotionsParamsType = z.infer<
typeof AdminGetPromotionsParams
>
export const AdminGetPromotionsParams = createFindParams({
limit: 50,
offset: 0,
}).merge(
z.object({
q: z.string().optional(),
code: z.union([z.string(), z.array(z.string())]).optional(),
created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(),
$and: z.lazy(() => AdminGetPromotionsParams.array()).optional(),
$or: z.lazy(() => AdminGetPromotionsParams.array()).optional(),
})
)
export type AdminGetPromotionRuleTypeParamsType = z.infer<
typeof AdminGetPromotionRuleTypeParams
>
export const AdminGetPromotionRuleTypeParams = createSelectParams()
export type AdminGetPromotionsRuleValueParamsType = z.infer<
typeof AdminGetPromotionsRuleValueParams
>
export const AdminGetPromotionsRuleValueParams = createFindParams({
limit: 100, limit: 100,
offset: 0, offset: 0,
}) { }).merge(
/** z.object({
* Search terms to search fields. q: z.string().optional(),
*/ })
@IsString() )
@IsOptional()
q?: string export type AdminCreatePromotionRuleType = z.infer<
typeof AdminCreatePromotionRule
>
export const AdminCreatePromotionRule = z
.object({
operator: z.nativeEnum(PromotionRuleOperator),
description: z.string().optional(),
attribute: z.string(),
values: z.array(z.string()),
})
.strict()
export type AdminUpdatePromotionRuleType = z.infer<
typeof AdminUpdatePromotionRule
>
export const AdminUpdatePromotionRule = z
.object({
id: z.string(),
operator: z.nativeEnum(PromotionRuleOperator).optional(),
description: z.string().optional(),
attribute: z.string().optional(),
values: z.array(z.string()).optional(),
})
.strict()
export type AdminCreateApplicationMethodType = z.infer<
typeof AdminCreateApplicationMethod
>
export const AdminCreateApplicationMethod = z
.object({
description: z.string().optional(),
value: z.string(),
max_quantity: z.number().optional(),
type: z.nativeEnum(ApplicationMethodType),
target_type: z.nativeEnum(ApplicationMethodTargetType),
allocation: z.nativeEnum(ApplicationMethodAllocation).optional(),
target_rules: z.array(AdminCreatePromotionRule).optional(),
buy_rules: z.array(AdminCreatePromotionRule).optional(),
apply_to_quantity: z.number().optional(),
buy_rules_min_quantity: z.number().optional(),
})
.strict()
export type AdminUpdateApplicationMethodType = z.infer<
typeof AdminUpdateApplicationMethod
>
export const AdminUpdateApplicationMethod = z
.object({
description: z.string().optional(),
value: z.string().optional(),
max_quantity: z.number().optional(),
type: z.nativeEnum(ApplicationMethodType).optional(),
target_type: z.nativeEnum(ApplicationMethodTargetType).optional(),
allocation: z.nativeEnum(ApplicationMethodAllocation).optional(),
target_rules: z.array(AdminCreatePromotionRule).optional(),
buy_rules: z.array(AdminCreatePromotionRule).optional(),
apply_to_quantity: z.number().optional(),
buy_rules_min_quantity: z.number().optional(),
})
.strict()
const promoRefinement = (promo) => {
if (promo.campaign && promo.campaign_id) {
return false
}
if (promo.type === PromotionType.BUYGET) {
const appMethod = promo.application_method
return (
(appMethod?.buy_rules?.length ?? 0) > 0 &&
appMethod?.apply_to_quantity !== undefined &&
appMethod?.buy_rules_min_quantity !== undefined
)
}
return true
} }
export class AdminGetPromotionsParams extends extendedFindParamsMixin({ // Ideally we don't allow for creation of campaigns through promotions, it should be the other way around.
limit: 100, const CreateCampaignBudget = z.object({
offset: 0, type: z.nativeEnum(CampaignBudgetType),
}) { limit: z.number(),
@IsString() })
@IsOptional()
code?: string
/** export type AdminCreateCampaignType = z.infer<typeof AdminCreateCampaign>
* Search terms to search promotions' code fields. export const AdminCreateCampaign = z.object({
*/ name: z.string(),
@IsString() campaign_identifier: z.string(),
@IsOptional() description: z.string().optional(),
q?: string currency: z.string().optional(),
budget: CreateCampaignBudget.optional(),
starts_at: z.coerce.date().optional(),
ends_at: z.coerce.date().optional(),
promotions: z.array(z.object({ id: z.string() })).optional(),
})
/** export type AdminCreatePromotionType = z.infer<typeof AdminCreatePromotion>
* Date filters to apply on the promotions' `created_at` date. export const AdminCreatePromotion = z
*/ .object({
@IsOptional() code: z.string(),
@ValidateNested() is_automatic: z.boolean().optional(),
@Type(() => DateComparisonOperator) type: z.nativeEnum(PromotionType),
created_at?: DateComparisonOperator campaign_id: z.string().optional(),
campaign: AdminCreateCampaign.optional(),
application_method: AdminCreateApplicationMethod,
rules: z.array(AdminCreatePromotionRule).optional(),
})
.strict()
// In the case of a buyget promotion, we require at least one buy rule and quantities
.refine(promoRefinement, {
message:
"Buyget promotions require at least one buy rule and quantities to be defined",
})
/** export type AdminUpdatePromotionType = z.infer<typeof AdminUpdatePromotion>
* Date filters to apply on the promotions' `updated_at` date. export const AdminUpdatePromotion = z
*/ .object({
@IsOptional() code: z.string().optional(),
@ValidateNested() is_automatic: z.boolean().optional(),
@Type(() => DateComparisonOperator) type: z.nativeEnum(PromotionType).optional(),
updated_at?: DateComparisonOperator campaign_id: z.string().optional(),
} campaign: AdminCreateCampaign.optional(),
application_method: AdminUpdateApplicationMethod.optional(),
rules: z.array(AdminCreatePromotionRule).optional(),
})
.strict()
// In the case of a buyget promotion, we require at least one buy rule and quantities
.refine(promoRefinement, {
message:
"Buyget promotions require at least one buy rule and quantities to be defined",
})
export class AdminPostCreatePromotionRule { export type AdminCreateBatchRulesType = z.infer<typeof AdminCreateBatchRules>
@IsEnum(PromotionRuleOperator) export const AdminCreateBatchRules = z
operator: PromotionRuleOperator .object({
rules: z.array(AdminCreatePromotionRule).min(1),
})
.strict()
@IsOptional() export type AdminUpdateBatchRulesType = z.infer<typeof AdminUpdateBatchRules>
@IsString() export const AdminUpdateBatchRules = z
description?: string | null .object({
rules: z.array(AdminUpdatePromotionRule).min(1),
})
.strict()
@IsNotEmpty() export type AdminRemoveBatchRulesType = z.infer<typeof AdminRemoveBatchRules>
@IsString() export const AdminRemoveBatchRules = z
attribute: string .object({
rule_ids: z.array(z.string()).min(1),
@IsArray() })
@Type(() => String) .strict()
values: string[]
}
export class AdminPostCampaignsReq {
@IsNotEmpty()
@IsString()
name: string
@IsOptional()
@IsNotEmpty()
campaign_identifier?: string
@IsOptional()
@IsString()
description?: string
@IsOptional()
@IsString()
currency?: string
@IsOptional()
@ValidateNested()
@Type(() => CampaignBudget)
budget?: CampaignBudget
@IsOptional()
@IsDateString()
starts_at?: string
@IsOptional()
@IsDateString()
ends_at?: string
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => IdObject)
promotions?: IdObject[]
}
export class IdObject {
@IsString()
@IsNotEmpty()
id: string
}
export class CampaignBudget {
@IsOptional()
@IsEnum(CampaignBudgetType)
type?: CampaignBudgetType
@IsOptional()
@IsNumber()
limit?: number
}
export class AdminPostPromotionsReq {
@IsNotEmpty()
@IsString()
code: string
@IsBoolean()
@IsOptional()
is_automatic?: boolean
@IsEnum(PromotionType)
type?: PromotionTypeValues
@IsOptional()
@IsString()
campaign_id?: string
@IsOptional()
@ValidateNested()
@Type(() => AdminPostCampaignsReq)
campaign?: AdminPostCampaignsReq
@IsNotEmpty()
@ValidateNested()
@Type(() => AdminPostApplicationMethodsReq)
application_method: AdminPostApplicationMethodsReq
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => AdminPostCreatePromotionRule)
rules?: AdminPostCreatePromotionRule[]
}
export class AdminPostApplicationMethodsReq {
@IsOptional()
@IsString()
description?: string
@IsOptional()
@IsString()
value?: string
@IsOptional()
@IsNumber()
max_quantity?: number
@IsOptional()
@IsEnum(ApplicationMethodType)
type?: ApplicationMethodType
@IsEnum(ApplicationMethodTargetType)
target_type?: ApplicationMethodTargetType
@IsOptional()
@IsEnum(ApplicationMethodAllocation)
allocation?: ApplicationMethodAllocation
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => AdminPostCreatePromotionRule)
target_rules?: AdminPostCreatePromotionRule[]
@ValidateIf((data) => data.type === PromotionType.BUYGET)
@IsArray()
@ValidateNested({ each: true })
@Type(() => AdminPostCreatePromotionRule)
buy_rules?: AdminPostCreatePromotionRule[]
@ValidateIf((data) => data.type === PromotionType.BUYGET)
@IsNotEmpty()
@IsNumber()
apply_to_quantity?: number
@ValidateIf((data) => data.type === PromotionType.BUYGET)
@IsNotEmpty()
@IsNumber()
buy_rules_min_quantity?: number
}
export class AdminPostApplicationMethodsMethodReq {
@IsOptional()
@IsString()
description?: string
@IsOptional()
@IsString()
value?: string
@IsOptional()
@IsNumber()
max_quantity?: number
@IsOptional()
@IsEnum(ApplicationMethodType)
type?: ApplicationMethodType
@IsOptional()
@IsEnum(ApplicationMethodTargetType)
target_type?: ApplicationMethodTargetType
@IsOptional()
@IsEnum(ApplicationMethodAllocation)
allocation?: ApplicationMethodAllocation
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => AdminPostCreatePromotionRule)
target_rules?: AdminPostCreatePromotionRule[]
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => AdminPostCreatePromotionRule)
buy_rules?: AdminPostCreatePromotionRule[]
@IsOptional()
@IsNumber()
apply_to_quantity?: number
@IsOptional()
@IsNumber()
buy_rules_min_quantity?: number
}
export class AdminPostPromotionsPromotionReq {
@IsOptional()
@IsString()
code?: string
@IsOptional()
@IsBoolean()
is_automatic?: boolean
@IsOptional()
@IsEnum(PromotionType)
type?: PromotionType
@IsOptional()
@Validate(XorConstraint, ["campaign"])
@IsString()
campaign_id?: string
@IsOptional()
@Validate(XorConstraint, ["campaign_id"])
@ValidateNested()
@Type(() => AdminPostCampaignsReq)
campaign?: AdminPostCampaignsReq
@IsOptional()
@ValidateNested()
@Type(() => AdminPostApplicationMethodsMethodReq)
application_method?: AdminPostApplicationMethodsMethodReq
@IsOptional()
@IsArray()
@ValidateNested({ each: true })
@Type(() => AdminPostCreatePromotionRule)
rules?: AdminPostCreatePromotionRule[]
}
export class AdminPostBatchAddRules {
@IsArray()
@ValidateNested({ each: true })
@Type(() => AdminPostCreatePromotionRule)
rules: AdminPostCreatePromotionRule[]
}
export class AdminPostBatchRemoveRules {
@ArrayNotEmpty()
@IsString({ each: true })
rule_ids: string[]
}
export class AdminPostBatchUpdateRules {
@IsArray()
@ValidateNested({ each: true })
@Type(() => AdminPostUpdatePromotionRule)
rules: AdminPostUpdatePromotionRule[]
}
export class AdminPostUpdatePromotionRule {
@IsNotEmpty()
@IsString()
id: string
@IsOptional()
@IsEnum(PromotionRuleOperator)
operator?: PromotionRuleOperator
@IsOptional()
@IsString()
description?: string | null
@IsOptional()
@IsNotEmpty()
@IsString()
attribute?: string
@IsOptional()
@IsArray()
@Type(() => String)
values?: string[]
}
@@ -3,13 +3,14 @@ import {
MedusaResponse, MedusaResponse,
} from "../../../../types/routing" } from "../../../../types/routing"
import { deleteFilesWorkflow } from "@medusajs/core-flows" import { deleteFilesWorkflow } from "@medusajs/core-flows"
import { ContainerRegistrationKeys } from "@medusajs/utils"
import { remoteQueryObjectFromString } from "@medusajs/utils" import { remoteQueryObjectFromString } from "@medusajs/utils"
export const GET = async ( export const GET = async (
req: AuthenticatedMedusaRequest, req: AuthenticatedMedusaRequest,
res: MedusaResponse res: MedusaResponse
) => { ) => {
const remoteQuery = req.scope.resolve("remoteQuery") const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const variables = { id: req.params.id } const variables = { id: req.params.id }
const queryObject = remoteQueryObjectFromString({ const queryObject = remoteQueryObjectFromString({
-1
View File
@@ -1 +0,0 @@
export * from "./admin"
@@ -0,0 +1,26 @@
import { z } from "zod"
export const AddressPayload = z
.object({
first_name: z.string().optional(),
last_name: z.string().optional(),
phone: z.string().optional(),
company: z.string().optional(),
address_1: z.string().optional(),
address_2: z.string().optional(),
city: z.string().optional(),
country_code: z.string().optional(),
province: z.string().optional(),
postal_code: z.string().optional(),
metadata: z.record(z.string()).optional(),
})
.strict()
export const BigNumberInput = z.union([
z.number(),
z.string(),
z.object({
value: z.string(),
precision: z.number(),
}),
])
@@ -4,17 +4,12 @@ import { z, ZodError } from "zod"
import { MedusaRequest, MedusaResponse } from "../../types/routing" import { MedusaRequest, MedusaResponse } from "../../types/routing"
export async function zodValidator<T>( export async function zodValidator<T>(
zodSchema: z.ZodObject<any, any>, zodSchema: z.ZodObject<any, any> | z.ZodEffects<any, any>,
body: T, body: T
config: { strict?: boolean } = { strict: true }
): Promise<z.ZodRawShape> { ): Promise<z.ZodRawShape> {
try { try {
let schema = zodSchema zodSchema
if (config.strict) { return await zodSchema.parseAsync(body)
schema = schema.strict()
}
return await schema.parseAsync(body)
} catch (err) { } catch (err) {
if (err instanceof ZodError) { if (err instanceof ZodError) {
throw new MedusaError( throw new MedusaError(
@@ -28,10 +23,7 @@ export async function zodValidator<T>(
} }
export function validateAndTransformBody( export function validateAndTransformBody(
zodSchema: z.ZodObject<any, any>, zodSchema: z.ZodObject<any, any> | z.ZodEffects<any, any>
config?: {
strict?: boolean
}
): ( ): (
req: MedusaRequest, req: MedusaRequest,
res: MedusaResponse, res: MedusaResponse,
@@ -39,7 +31,7 @@ export function validateAndTransformBody(
) => Promise<void> { ) => Promise<void> {
return async (req: MedusaRequest, _: MedusaResponse, next: NextFunction) => { return async (req: MedusaRequest, _: MedusaResponse, next: NextFunction) => {
try { try {
req.validatedBody = await zodValidator(zodSchema, req.body, config) req.validatedBody = await zodValidator(zodSchema, req.body)
next() next()
} catch (e) { } catch (e) {
next(e) next(e)
@@ -34,11 +34,8 @@ const getFilterableFields = <T extends RequestQueryFields>(obj: T): T => {
} }
export function validateAndTransformQuery<TEntity extends BaseEntity>( export function validateAndTransformQuery<TEntity extends BaseEntity>(
zodSchema: z.ZodObject<any, any>, zodSchema: z.ZodObject<any, any> | z.ZodEffects<any, any>,
queryConfig: QueryConfig<TEntity>, queryConfig: QueryConfig<TEntity>
config?: {
strict?: boolean
}
): ( ): (
req: MedusaRequest, req: MedusaRequest,
res: MedusaResponse, res: MedusaResponse,
@@ -48,7 +45,7 @@ export function validateAndTransformQuery<TEntity extends BaseEntity>(
try { try {
const query = normalizeQuery(req) const query = normalizeQuery(req)
const validated = await zodValidator(zodSchema, query, config) const validated = await zodValidator(zodSchema, query)
const cnf = queryConfig.isList const cnf = queryConfig.isList
? prepareListQuery(validated, queryConfig) ? prepareListQuery(validated, queryConfig)
: prepareRetrieveQuery(validated, queryConfig) : prepareRetrieveQuery(validated, queryConfig)
-1
View File
@@ -1,5 +1,4 @@
export * from "./api" export * from "./api"
export * from "./api-v2"
export * from "./api/middlewares" export * from "./api/middlewares"
export * from "./interfaces" export * from "./interfaces"
export * from "./models" export * from "./models"
+8 -1
View File
@@ -1,13 +1,14 @@
import { Modules } from "@medusajs/modules-sdk" import { Modules } from "@medusajs/modules-sdk"
import { ModuleJoinerConfig } from "@medusajs/types" import { ModuleJoinerConfig } from "@medusajs/types"
import { MapToConfig } from "@medusajs/utils" import { MapToConfig } from "@medusajs/utils"
import { Price, PriceList, PriceSet } from "@models" import { Price, PriceList, PriceSet, RuleType } from "@models"
import schema from "./schema" import schema from "./schema"
export const LinkableKeys = { export const LinkableKeys = {
price_set_id: PriceSet.name, price_set_id: PriceSet.name,
price_list_id: PriceList.name, price_list_id: PriceList.name,
price_id: Price.name, price_id: Price.name,
rule_type_id: RuleType.name,
} }
const entityLinkableKeysMap: MapToConfig = {} const entityLinkableKeysMap: MapToConfig = {}
@@ -45,5 +46,11 @@ export const joinerConfig: ModuleJoinerConfig = {
methodSuffix: "Prices", methodSuffix: "Prices",
}, },
}, },
{
name: ["rule_type", "rule_types"],
args: {
methodSuffix: "RuleTypes",
},
},
], ],
} }