feature: introduce additional_data to the product endpoints (#8405)

This commit is contained in:
Harminder Virk
2024-08-05 09:24:49 +05:30
committed by GitHub
parent bcad5052af
commit 0706bab663
30 changed files with 219 additions and 258 deletions
@@ -8,7 +8,7 @@ import {
} from "@medusajs/core-flows"
import { remapKeysForProduct, remapProductResponse } from "../../../helpers"
import { HttpTypes } from "@medusajs/types"
import { AdditionalData, HttpTypes } from "@medusajs/types"
import { refetchEntity } from "../../../../../utils/refetch-entity"
export const GET = async (
@@ -28,16 +28,20 @@ export const GET = async (
}
export const POST = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminUpdateProductOption>,
req: AuthenticatedMedusaRequest<
HttpTypes.AdminUpdateProductOption & AdditionalData
>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const productId = req.params.id
const optionId = req.params.option_id
const { additional_data, ...update } = req.validatedBody
await updateProductOptionsWorkflow(req.scope).run({
input: {
selector: { id: optionId, product_id: productId },
update: req.validatedBody,
update,
additional_data,
},
})
@@ -5,7 +5,7 @@ import {
import { createProductOptionsWorkflow } from "@medusajs/core-flows"
import { remapKeysForProduct, remapProductResponse } from "../../helpers"
import { HttpTypes } from "@medusajs/types"
import { AdditionalData, HttpTypes } from "@medusajs/types"
import {
refetchEntities,
refetchEntity,
@@ -33,18 +33,23 @@ export const GET = async (
}
export const POST = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminCreateProductOption>,
req: AuthenticatedMedusaRequest<
HttpTypes.AdminCreateProductOption & AdditionalData
>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const productId = req.params.id
const { additional_data, ...rest } = req.validatedBody
await createProductOptionsWorkflow(req.scope).run({
input: {
product_options: [
{
...req.validatedBody,
...rest,
product_id: productId,
},
],
additional_data,
},
})
@@ -8,7 +8,7 @@ import {
} from "../../../../types/routing"
import { remapKeysForProduct, remapProductResponse } from "../helpers"
import { MedusaError } from "@medusajs/utils"
import { HttpTypes } from "@medusajs/types"
import { AdditionalData, HttpTypes } from "@medusajs/types"
import { refetchEntity } from "../../../utils/refetch-entity"
export const GET = async (
@@ -31,13 +31,18 @@ export const GET = async (
}
export const POST = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminUpdateProduct>,
req: AuthenticatedMedusaRequest<
HttpTypes.AdminUpdateProduct & AdditionalData
>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const { additional_data, ...update } = req.validatedBody
const { result } = await updateProductsWorkflow(req.scope).run({
input: {
selector: { id: req.params.id },
update: req.validatedBody,
update,
additional_data,
},
})
@@ -7,7 +7,7 @@ import {
MedusaResponse,
} from "../../../../../../types/routing"
import { HttpTypes } from "@medusajs/types"
import { AdditionalData, HttpTypes } from "@medusajs/types"
import { refetchEntity } from "../../../../../utils/refetch-entity"
import {
remapKeysForProduct,
@@ -35,16 +35,20 @@ export const GET = async (
}
export const POST = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminUpdateProductVariant>,
req: AuthenticatedMedusaRequest<
HttpTypes.AdminUpdateProductVariant & AdditionalData
>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const productId = req.params.id
const variantId = req.params.variant_id
const { additional_data, ...update } = req.validatedBody
await updateProductVariantsWorkflow(req.scope).run({
input: {
selector: { id: variantId, product_id: productId },
update: req.validatedBody,
update: update,
additional_data,
},
})
@@ -1,5 +1,5 @@
import { createProductVariantsWorkflow } from "@medusajs/core-flows"
import { HttpTypes } from "@medusajs/types"
import { AdditionalData, HttpTypes } from "@medusajs/types"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
@@ -53,19 +53,23 @@ export const GET = async (
}
export const POST = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminCreateProductVariant>,
req: AuthenticatedMedusaRequest<
HttpTypes.AdminCreateProductVariant & AdditionalData
>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const productId = req.params.id
const { additional_data, ...rest } = req.validatedBody
const input = [
{
...req.validatedBody,
...rest,
product_id: productId,
},
]
await createProductVariantsWorkflow(req.scope).run({
input: { product_variants: input },
input: { product_variants: input, additional_data },
})
const product = await refetchEntity(
@@ -27,6 +27,8 @@ import {
AdminUpdateProductOption,
AdminUpdateProductVariant,
AdminUpdateVariantInventoryItem,
CreateProduct,
CreateProductVariant,
} from "./validators"
import multer from "multer"
@@ -68,7 +70,7 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
matcher: "/admin/products/batch",
middlewares: [
validateAndTransformBody(
createBatchBody(AdminCreateProduct, AdminBatchUpdateProduct)
createBatchBody(CreateProduct, AdminBatchUpdateProduct)
),
validateAndTransformQuery(
AdminGetProductParams,
@@ -166,10 +168,7 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
matcher: "/admin/products/:id/variants/batch",
middlewares: [
validateAndTransformBody(
createBatchBody(
AdminCreateProductVariant,
AdminBatchUpdateProductVariant
)
createBatchBody(CreateProductVariant, AdminBatchUpdateProductVariant)
),
validateAndTransformQuery(
AdminGetProductVariantParams,
@@ -1,5 +1,5 @@
import { createProductsWorkflow } from "@medusajs/core-flows"
import { HttpTypes } from "@medusajs/types"
import { AdditionalData, HttpTypes } from "@medusajs/types"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
@@ -30,11 +30,15 @@ export const GET = async (
}
export const POST = async (
req: AuthenticatedMedusaRequest<HttpTypes.AdminCreateProduct>,
req: AuthenticatedMedusaRequest<
HttpTypes.AdminCreateProduct & AdditionalData
>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const { additional_data, ...products } = req.validatedBody
const { result } = await createProductsWorkflow(req.scope).run({
input: { products: [req.validatedBody] },
input: { products: [products], additional_data },
})
const product = await refetchEntity(
@@ -1,10 +1,10 @@
import { BatchMethodRequest } from "@medusajs/types"
import { ProductStatus } from "@medusajs/utils"
import { z } from "zod"
import {
GetProductsParams,
transformProductParams,
} from "../../utils/common-validators"
import { z, ZodObject } from "zod"
import {
createFindParams,
createOperatorMap,
@@ -85,23 +85,46 @@ export const AdminUpdateProductTag = z.object({
value: z.string().optional(),
})
export type AdminCreateProductOptionType = z.infer<
typeof AdminCreateProductOption
>
export const AdminCreateProductOption = z.object({
export type AdminCreateProductOptionType = z.infer<typeof CreateProductOption>
export const CreateProductOption = z.object({
title: z.string(),
values: z.array(z.string()),
})
export const AdminCreateProductOption = (
additionalDataValidator?: ZodObject<any, any>
) => {
if (!additionalDataValidator) {
return CreateProductOption.extend({
additional_data: z.record(z.unknown()).nullish(),
})
}
export type AdminUpdateProductOptionType = z.infer<
typeof AdminUpdateProductOption
>
export const AdminUpdateProductOption = z.object({
return CreateProductOption.extend({
additional_data: additionalDataValidator,
})
}
export type AdminUpdateProductOptionType = z.infer<typeof UpdateProductOption>
export const UpdateProductOption = z.object({
id: z.string().optional(),
title: z.string().optional(),
values: z.array(z.string()).optional(),
})
export const AdminUpdateProductOption = (
additionalDataValidator?: ZodObject<any, any>
) => {
if (!additionalDataValidator) {
return UpdateProductOption.extend({
additional_data: z.record(z.unknown()).nullish(),
})
}
return UpdateProductOption.extend({
additional_data: additionalDataValidator,
})
}
export type AdminCreateVariantPriceType = z.infer<
typeof AdminCreateVariantPrice
>
@@ -130,10 +153,8 @@ export const AdminCreateProductType = z.object({
value: z.string(),
})
export type AdminCreateProductVariantType = z.infer<
typeof AdminCreateProductVariant
>
export const AdminCreateProductVariant = z
export type AdminCreateProductVariantType = z.infer<typeof CreateProductVariant>
export const CreateProductVariant = z
.object({
title: z.string(),
sku: z.string().nullish(),
@@ -164,11 +185,22 @@ export const AdminCreateProductVariant = z
.optional(),
})
.strict()
export const AdminCreateProductVariant = (
additionalDataValidator?: ZodObject<any, any>
) => {
if (!additionalDataValidator) {
return CreateProductVariant.extend({
additional_data: z.record(z.string()).optional(),
})
}
export type AdminUpdateProductVariantType = z.infer<
typeof AdminUpdateProductVariant
>
export const AdminUpdateProductVariant = z
return CreateProductVariant.extend({
additional_data: additionalDataValidator,
})
}
export type AdminUpdateProductVariantType = z.infer<typeof UpdateProductVariant>
export const UpdateProductVariant = z
.object({
id: z.string().optional(),
title: z.string().optional(),
@@ -193,10 +225,24 @@ export const AdminUpdateProductVariant = z
})
.strict()
export const AdminUpdateProductVariant = (
additionalDataValidator?: ZodObject<any, any>
) => {
if (!additionalDataValidator) {
return UpdateProductVariant.extend({
additional_data: z.record(z.string()).optional(),
})
}
return UpdateProductVariant.extend({
additional_data: additionalDataValidator,
})
}
export type AdminBatchUpdateProductVariantType = z.infer<
typeof AdminBatchUpdateProductVariant
>
export const AdminBatchUpdateProductVariant = AdminUpdateProductVariant.extend({
export const AdminBatchUpdateProductVariant = UpdateProductVariant.extend({
id: z.string(),
})
@@ -204,8 +250,8 @@ export const IdAssociation = z.object({
id: z.string(),
})
export type AdminCreateProductType = z.infer<typeof AdminCreateProduct>
export const AdminCreateProduct = z
export type AdminCreateProductType = z.infer<typeof CreateProduct>
export const CreateProduct = z
.object({
title: z.string(),
subtitle: z.string().nullish(),
@@ -220,8 +266,8 @@ export const AdminCreateProduct = z
collection_id: z.string().nullish(),
categories: z.array(IdAssociation).optional(),
tags: z.array(IdAssociation).optional(),
options: z.array(AdminCreateProductOption).optional(),
variants: z.array(AdminCreateProductVariant).optional(),
options: z.array(CreateProductOption).optional(),
variants: z.array(CreateProductVariant).optional(),
sales_channels: z.array(z.object({ id: z.string() })).optional(),
weight: z.number().nullish(),
length: z.number().nullish(),
@@ -235,14 +281,28 @@ export const AdminCreateProduct = z
})
.strict()
export type AdminUpdateProductType = z.infer<typeof AdminUpdateProduct>
export const AdminUpdateProduct = z
export const AdminCreateProduct = (
additionalDataValidator?: ZodObject<any, any>
) => {
if (!additionalDataValidator) {
return CreateProduct.extend({
additional_data: z.record(z.unknown()).nullish(),
})
}
return CreateProduct.extend({
additional_data: additionalDataValidator,
})
}
export type AdminUpdateProductType = z.infer<typeof UpdateProduct>
export const UpdateProduct = z
.object({
title: z.string().optional(),
discountable: z.boolean().optional(),
is_giftcard: z.boolean().optional(),
options: z.array(AdminUpdateProductOption).optional(),
variants: z.array(AdminUpdateProductVariant).optional(),
options: z.array(UpdateProductOption).optional(),
variants: z.array(UpdateProductVariant).optional(),
status: statusEnum.optional(),
subtitle: z.string().nullish(),
description: z.string().nullish(),
@@ -266,10 +326,24 @@ export const AdminUpdateProduct = z
})
.strict()
export const AdminUpdateProduct = (
additionalDataValidator?: ZodObject<any, any>
) => {
if (!additionalDataValidator) {
return UpdateProduct.extend({
additional_data: z.record(z.unknown()).nullish(),
})
}
return UpdateProduct.extend({
additional_data: additionalDataValidator,
})
}
export type AdminBatchUpdateProductType = z.infer<
typeof AdminBatchUpdateProduct
>
export const AdminBatchUpdateProduct = AdminUpdateProduct.extend({
export const AdminBatchUpdateProduct = UpdateProduct.extend({
id: z.string(),
})
@@ -9,7 +9,7 @@ describe("validateAndTransformBody", () => {
jest.clearAllMocks()
})
it("should merge a custom validators schema", async () => {
it("should pass additionalDataValidator to validator factory", async () => {
let mockRequest = {
query: {},
body: {},
@@ -18,42 +18,16 @@ describe("validateAndTransformBody", () => {
const mockResponse = {} as MedusaResponse
const nextFunction = jest.fn()
mockRequest.extendedValidators = {
body: zod.object({
brand_id: zod.number(),
}),
}
let middleware = validateAndTransformBody(createLinkBody())
await middleware(mockRequest, mockResponse, nextFunction)
expect(nextFunction).toHaveBeenCalledWith(
new MedusaError(
"invalid_data",
`Invalid request: Field 'brand_id' is required`
)
)
})
it("should pass schema to merge to the original validator factory", async () => {
let mockRequest = {
query: {},
body: {},
} as MedusaRequest
const mockResponse = {} as MedusaResponse
const nextFunction = jest.fn()
mockRequest.extendedValidators = {
body: zod.object({
brand_id: zod.number(),
}),
}
mockRequest.additionalDataValidator = zod.object({
brand_id: zod.number(),
})
const validatorFactory = (schema?: Zod.ZodObject<any, any>) => {
return schema ? createLinkBody().merge(schema) : createLinkBody()
}
let middleware = validateAndTransformBody(validatorFactory)
await middleware(mockRequest, mockResponse, nextFunction)
expect(nextFunction).toHaveBeenCalledWith(
new MedusaError(
@@ -695,56 +695,4 @@ describe("validateAndTransformQuery", () => {
)
)
})
it("should merge a custom validators schema", async () => {
let mockRequest = {
query: {},
} as MedusaRequest
const mockResponse = {} as MedusaResponse
const nextFunction = jest.fn()
mockRequest.extendedValidators = {
queryParams: zod.object({
page: zod.number(),
}),
}
let middleware = validateAndTransformQuery(createFindParams(), {})
await middleware(mockRequest, mockResponse, nextFunction)
expect(nextFunction).toHaveBeenCalledWith(
new MedusaError(
"invalid_data",
`Invalid request: Field 'page' is required`
)
)
})
it("should pass schema to merge to the original validator factory", async () => {
let mockRequest = {
query: {},
} as MedusaRequest
const mockResponse = {} as MedusaResponse
const nextFunction = jest.fn()
mockRequest.extendedValidators = {
queryParams: zod.object({
page: zod.number(),
}),
}
const validatorFactory = (schema?: Zod.ZodObject<any, any>) => {
return schema ? createFindParams().merge(schema) : createFindParams()
}
let middleware = validateAndTransformQuery(validatorFactory, {})
await middleware(mockRequest, mockResponse, nextFunction)
expect(nextFunction).toHaveBeenCalledWith(
new MedusaError(
"invalid_data",
`Invalid request: Field 'page' is required`
)
)
})
})
@@ -17,12 +17,8 @@ export function validateAndTransformBody(
return async (req: MedusaRequest, _: MedusaResponse, next: NextFunction) => {
try {
let schema: z.ZodObject<any, any> | z.ZodEffects<any, any>
const { body: bodyValidatorToMerge } = req.extendedValidators ?? {}
if (typeof zodSchema === "function") {
schema = zodSchema(bodyValidatorToMerge)
} else if (bodyValidatorToMerge) {
schema = zodSchema.merge(bodyValidatorToMerge)
schema = zodSchema(req.additionalDataValidator)
} else {
schema = zodSchema
}
@@ -58,11 +58,7 @@ const getFilterableFields = <T extends RequestQueryFields>(obj: T): T => {
}
export function validateAndTransformQuery<TEntity extends BaseEntity>(
zodSchema:
| z.ZodObject<any, any>
| ((
customSchema?: z.ZodObject<any, any>
) => z.ZodObject<any, any> | z.ZodEffects<any, any>),
zodSchema: z.ZodObject<any, any> | z.ZodEffects<any, any>,
queryConfig: QueryConfig<TEntity>
): (
req: MedusaRequest,
@@ -75,18 +71,7 @@ export function validateAndTransformQuery<TEntity extends BaseEntity>(
delete req.allowed
const query = normalizeQuery(req)
let schema: z.ZodObject<any, any> | z.ZodEffects<any, any>
const { queryParams: queryParamsToMerge } = req.extendedValidators ?? {}
if (typeof zodSchema === "function") {
schema = zodSchema(queryParamsToMerge)
} else if (queryParamsToMerge) {
schema = zodSchema.merge(queryParamsToMerge)
} else {
schema = zodSchema
}
const validated = await zodValidator(schema, query)
const validated = await zodValidator(zodSchema, query)
const cnf = queryConfig.isList
? prepareListQuery(validated, { ...queryConfig, allowed })
: prepareRetrieveQuery(validated, { ...queryConfig, allowed })