feat: Add support for batch method for products and product variants (#7038)
* feat(products): Add batch methods for product and variants * chore: Rename batch validator and minor changes based on PR review
This commit is contained in:
1 parent
c3efac5a0d
commit
fd83e75e4b
25 files changed
+793
-56
No files matched your search
@@ -64,7 +64,6 @@ export const POST = async (
|
||||
req.remoteQueryConfig.fields
|
||||
)
|
||||
res.status(200).json({ product: remapProductResponse(product) })
|
||||
Response
|
||||
}
|
||||
|
||||
export const DELETE = async (
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { batchProductVariantsWorkflow } from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../../../types/routing"
|
||||
import {
|
||||
AdminBatchUpdateProductVariantType,
|
||||
AdminCreateProductType,
|
||||
} from "../../../../validators"
|
||||
import { BatchMethodRequest } from "@medusajs/types"
|
||||
import { refetchBatchVariants, remapVariantResponse } from "../../../../helpers"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<
|
||||
BatchMethodRequest<
|
||||
AdminCreateProductType,
|
||||
AdminBatchUpdateProductVariantType
|
||||
>
|
||||
>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const productId = req.params.id
|
||||
|
||||
const normalizedInput = {
|
||||
create: req.validatedBody.create?.map((c) => ({
|
||||
...c,
|
||||
product_id: productId,
|
||||
})),
|
||||
update: req.validatedBody.update?.map((u) => ({
|
||||
...u,
|
||||
product_id: productId,
|
||||
})),
|
||||
delete: req.validatedBody.delete,
|
||||
// TODO: Fix types
|
||||
} as any
|
||||
|
||||
const { result, errors } = await batchProductVariantsWorkflow(req.scope).run({
|
||||
input: normalizedInput,
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
const batchResults = await refetchBatchVariants(
|
||||
result,
|
||||
req.scope,
|
||||
req.remoteQueryConfig.fields
|
||||
)
|
||||
|
||||
res.status(200).json({
|
||||
created: batchResults.created.map(remapVariantResponse),
|
||||
updated: batchResults.updated.map(remapVariantResponse),
|
||||
deleted: batchResults.deleted,
|
||||
})
|
||||
}
|
||||
@@ -4,7 +4,10 @@ import {
|
||||
} from "../../../../../types/routing"
|
||||
|
||||
import { createProductVariantsWorkflow } from "@medusajs/core-flows"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
import {
|
||||
remoteQueryObjectFromString,
|
||||
ContainerRegistrationKeys,
|
||||
} from "@medusajs/utils"
|
||||
import {
|
||||
refetchProduct,
|
||||
remapKeysForVariant,
|
||||
@@ -17,7 +20,7 @@ export const GET = async (
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve("remoteQuery")
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
const productId = req.params.id
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { MedusaContainer, ProductDTO, ProductVariantDTO } from "@medusajs/types"
|
||||
import { remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
import {
|
||||
BatchMethodResponse,
|
||||
MedusaContainer,
|
||||
ProductDTO,
|
||||
ProductVariantDTO,
|
||||
} from "@medusajs/types"
|
||||
import { ContainerRegistrationKeys } from "@medusajs/utils"
|
||||
import { promiseAll, remoteQueryObjectFromString } from "@medusajs/utils"
|
||||
|
||||
const isPricing = (fieldName: string) =>
|
||||
fieldName.startsWith("variants.prices") ||
|
||||
@@ -67,7 +73,7 @@ export const refetchProduct = async (
|
||||
scope: MedusaContainer,
|
||||
fields: string[]
|
||||
) => {
|
||||
const remoteQuery = scope.resolve("remoteQuery")
|
||||
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product",
|
||||
variables: {
|
||||
@@ -79,3 +85,85 @@ export const refetchProduct = async (
|
||||
const products = await remoteQuery(queryObject)
|
||||
return products[0]
|
||||
}
|
||||
|
||||
export const refetchBatchProducts = async (
|
||||
batchResult: BatchMethodResponse<ProductDTO>,
|
||||
scope: MedusaContainer,
|
||||
fields: string[]
|
||||
) => {
|
||||
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
let created = Promise.resolve<ProductDTO[]>([])
|
||||
let updated = Promise.resolve<ProductDTO[]>([])
|
||||
|
||||
if (batchResult.created.length) {
|
||||
const createdQuery = remoteQueryObjectFromString({
|
||||
entryPoint: "product",
|
||||
variables: {
|
||||
filters: { id: batchResult.created.map((p) => p.id) },
|
||||
},
|
||||
fields: remapKeysForProduct(fields ?? []),
|
||||
})
|
||||
|
||||
created = remoteQuery(createdQuery)
|
||||
}
|
||||
|
||||
if (batchResult.updated.length) {
|
||||
const updatedQuery = remoteQueryObjectFromString({
|
||||
entryPoint: "product",
|
||||
variables: {
|
||||
filters: { id: batchResult.updated.map((p) => p.id) },
|
||||
},
|
||||
fields: remapKeysForProduct(fields ?? []),
|
||||
})
|
||||
|
||||
updated = remoteQuery(updatedQuery)
|
||||
}
|
||||
|
||||
const [createdRes, updatedRes] = await promiseAll([created, updated])
|
||||
return {
|
||||
created: createdRes,
|
||||
updated: updatedRes,
|
||||
deleted: batchResult.deleted,
|
||||
}
|
||||
}
|
||||
|
||||
export const refetchBatchVariants = async (
|
||||
batchResult: BatchMethodResponse<ProductVariantDTO>,
|
||||
scope: MedusaContainer,
|
||||
fields: string[]
|
||||
) => {
|
||||
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
let created = Promise.resolve<ProductVariantDTO[]>([])
|
||||
let updated = Promise.resolve<ProductVariantDTO[]>([])
|
||||
|
||||
if (batchResult.created.length) {
|
||||
const createdQuery = remoteQueryObjectFromString({
|
||||
entryPoint: "variant",
|
||||
variables: {
|
||||
filters: { id: batchResult.created.map((v) => v.id) },
|
||||
},
|
||||
fields: remapKeysForVariant(fields ?? []),
|
||||
})
|
||||
|
||||
created = remoteQuery(createdQuery)
|
||||
}
|
||||
|
||||
if (batchResult.updated.length) {
|
||||
const updatedQuery = remoteQueryObjectFromString({
|
||||
entryPoint: "variant",
|
||||
variables: {
|
||||
filters: { id: batchResult.updated.map((v) => v.id) },
|
||||
},
|
||||
fields: remapKeysForVariant(fields ?? []),
|
||||
})
|
||||
|
||||
updated = remoteQuery(updatedQuery)
|
||||
}
|
||||
|
||||
const [createdRes, updatedRes] = await promiseAll([created, updated])
|
||||
return {
|
||||
created: createdRes,
|
||||
updated: updatedRes,
|
||||
deleted: batchResult.deleted,
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import { transformBody, transformQuery } from "../../../api/middlewares"
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||
import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter"
|
||||
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||
import { createBatchBody } from "../../utils/validators"
|
||||
import * as QueryConfig from "./query-config"
|
||||
import { maybeApplyPriceListsFilter } from "./utils"
|
||||
import {
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
AdminUpdateProductVariant,
|
||||
AdminGetProductOptionsParams,
|
||||
AdminGetProductOptionParams,
|
||||
AdminBatchUpdateProduct,
|
||||
AdminBatchUpdateProductVariant,
|
||||
} from "./validators"
|
||||
|
||||
export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
@@ -64,6 +66,19 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/op/batch",
|
||||
middlewares: [
|
||||
validateAndTransformBody(
|
||||
createBatchBody(AdminCreateProduct, AdminBatchUpdateProduct)
|
||||
),
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id",
|
||||
@@ -96,6 +111,22 @@ export const adminProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["POST"],
|
||||
matcher: "/admin/products/:id/variants/op/batch",
|
||||
middlewares: [
|
||||
validateAndTransformBody(
|
||||
createBatchBody(
|
||||
AdminCreateProductVariant,
|
||||
AdminBatchUpdateProductVariant
|
||||
)
|
||||
),
|
||||
validateAndTransformQuery(
|
||||
AdminGetProductVariantParams,
|
||||
QueryConfig.retrieveVariantConfig
|
||||
),
|
||||
],
|
||||
},
|
||||
// Note: New endpoint in v2
|
||||
{
|
||||
method: ["GET"],
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { batchProductsWorkflow } from "@medusajs/core-flows"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "../../../../../types/routing"
|
||||
import {
|
||||
AdminBatchUpdateProductType,
|
||||
AdminCreateProductType,
|
||||
} from "../../validators"
|
||||
import { BatchMethodRequest } from "@medusajs/types"
|
||||
import { refetchBatchProducts, remapProductResponse } from "../../helpers"
|
||||
import { CreateProductDTO, UpsertProductDTO } from "@medusajs/types"
|
||||
|
||||
export const POST = async (
|
||||
req: AuthenticatedMedusaRequest<
|
||||
BatchMethodRequest<AdminCreateProductType, AdminBatchUpdateProductType>
|
||||
>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
// TODO: Fix types
|
||||
const input = req.validatedBody as any
|
||||
|
||||
const { result, errors } = await batchProductsWorkflow(req.scope).run({
|
||||
input,
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
if (Array.isArray(errors) && errors[0]) {
|
||||
throw errors[0].error
|
||||
}
|
||||
|
||||
const batchResults = await refetchBatchProducts(
|
||||
result,
|
||||
req.scope,
|
||||
req.remoteQueryConfig.fields
|
||||
)
|
||||
|
||||
res.status(200).json({
|
||||
created: batchResults.created.map(remapProductResponse),
|
||||
updated: batchResults.updated.map(remapProductResponse),
|
||||
deleted: batchResults.deleted,
|
||||
})
|
||||
}
|
||||
@@ -187,6 +187,13 @@ export const AdminUpdateProductVariant = AdminCreateProductVariant.extend({
|
||||
manage_inventory: z.boolean().optional(),
|
||||
}).strict()
|
||||
|
||||
export type AdminBatchUpdateProductVariantType = z.infer<
|
||||
typeof AdminBatchUpdateProductVariant
|
||||
>
|
||||
export const AdminBatchUpdateProductVariant = AdminUpdateProductVariant.extend({
|
||||
id: z.string(),
|
||||
})
|
||||
|
||||
export type AdminCreateProductType = z.infer<typeof AdminCreateProduct>
|
||||
export const AdminCreateProduct = z
|
||||
.object({
|
||||
@@ -228,6 +235,13 @@ export const AdminUpdateProduct = AdminCreateProduct.omit({ is_giftcard: true })
|
||||
})
|
||||
.strict()
|
||||
|
||||
export type AdminBatchUpdateProductType = z.infer<
|
||||
typeof AdminBatchUpdateProduct
|
||||
>
|
||||
export const AdminBatchUpdateProduct = AdminUpdateProduct.extend({
|
||||
id: z.string(),
|
||||
})
|
||||
|
||||
// TODO: Handle in create and update product once ready
|
||||
// @IsOptional()
|
||||
// @Type(() => ProductProductCategoryReq)
|
||||
|
||||
@@ -1,5 +1,16 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const createBatchBody = (
|
||||
createValidator: z.ZodType,
|
||||
updateValidator: z.ZodType
|
||||
) => {
|
||||
return z.object({
|
||||
create: z.array(createValidator).optional(),
|
||||
update: z.array(updateValidator).optional(),
|
||||
delete: z.array(z.string()).optional(),
|
||||
})
|
||||
}
|
||||
|
||||
export const createSelectParams = () => {
|
||||
return z.object({
|
||||
fields: z.string().optional(),
|
||||
|
||||
Reference in new issue
Block a user