Clean up product typings (#7663)

There are a lot of issues in the admin after applying the correct typings, but fixing those should be done gradually, it's better to keep it out of this PR
This commit is contained in:
Stevche Radevski
2024-06-11 09:28:40 +00:00
committed by GitHub
parent f3bf8c73a3
commit 8e2a42b786
88 changed files with 1205 additions and 990 deletions
@@ -7,37 +7,29 @@ import {
updateProductOptionsWorkflow,
} from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { refetchProduct, remapProductResponse } from "../../../helpers"
import { AdminUpdateProductOptionType } from "../../../validators"
import { remapKeysForProduct, remapProductResponse } from "../../../helpers"
import { HttpTypes } from "@medusajs/types"
import { refetchEntity } from "../../../../../utils/refetch-entity"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
res: MedusaResponse<HttpTypes.AdminProductOptionResponse>
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const productId = req.params.id
const optionId = req.params.option_id
const productOption = await refetchEntity(
"product_option",
{ id: optionId, product_id: productId },
req.scope,
req.remoteQueryConfig.fields
)
const variables = { id: optionId, product_id: productId }
const queryObject = remoteQueryObjectFromString({
entryPoint: "product_option",
variables,
fields: req.remoteQueryConfig.fields,
})
const [product_option] = await remoteQuery(queryObject)
res.status(200).json({ product_option })
res.status(200).json({ product_option: productOption })
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminUpdateProductOptionType>,
res: MedusaResponse
req: AuthenticatedMedusaRequest<HttpTypes.AdminUpdateProductOption>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const productId = req.params.id
const optionId = req.params.option_id
@@ -49,17 +41,19 @@ export const POST = async (
},
})
const product = await refetchProduct(
const product = await refetchEntity(
"product",
productId,
req.scope,
req.remoteQueryConfig.fields
remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
)
res.status(200).json({ product: remapProductResponse(product) })
}
export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
res: MedusaResponse<HttpTypes.AdminProductOptionDeleteResponse>
) => {
const productId = req.params.id
const optionId = req.params.option_id
@@ -69,10 +63,11 @@ export const DELETE = async (
input: { ids: [optionId] /* product_id: productId */ },
})
const product = await refetchProduct(
const product = await refetchEntity(
"product",
productId,
req.scope,
req.remoteQueryConfig.fields
remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
)
res.status(200).json({
@@ -4,30 +4,25 @@ import {
} from "../../../../../types/routing"
import { createProductOptionsWorkflow } from "@medusajs/core-flows"
import { remapKeysForProduct, remapProductResponse } from "../../helpers"
import { HttpTypes } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { refetchProduct, remapProductResponse } from "../../helpers"
import { AdminCreateProductOptionType } from "../../validators"
refetchEntities,
refetchEntity,
} from "../../../../utils/refetch-entity"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
req: AuthenticatedMedusaRequest<HttpTypes.AdminProductOptionParams>,
res: MedusaResponse<HttpTypes.AdminProductOptionListResponse>
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const productId = req.params.id
const queryObject = remoteQueryObjectFromString({
entryPoint: "product_option",
variables: {
filters: { ...req.filterableFields, product_id: productId },
...req.remoteQueryConfig.pagination,
},
fields: req.remoteQueryConfig.fields,
})
const { rows: product_options, metadata } = await remoteQuery(queryObject)
const { rows: product_options, metadata } = await refetchEntities(
"product_option",
{ ...req.filterableFields, product_id: productId },
req.scope,
req.remoteQueryConfig.fields,
req.remoteQueryConfig.pagination
)
res.json({
product_options,
@@ -38,25 +33,26 @@ export const GET = async (
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminCreateProductOptionType>,
res: MedusaResponse
req: AuthenticatedMedusaRequest<HttpTypes.AdminCreateProductOption>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const productId = req.params.id
const input = [
{
...req.validatedBody,
product_id: productId,
},
]
await createProductOptionsWorkflow(req.scope).run({
input: { product_options: input },
input: {
product_options: [
{
...req.validatedBody,
product_id: productId,
},
],
},
})
const product = await refetchProduct(
const product = await refetchEntity(
"product",
productId,
req.scope,
req.remoteQueryConfig.fields
remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
)
res.status(200).json({ product: remapProductResponse(product) })
}
@@ -6,36 +6,23 @@ import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import { UpdateProductDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
refetchProduct,
remapKeysForProduct,
remapProductResponse,
} from "../helpers"
import { AdminUpdateProductType } from "../validators"
import { remapKeysForProduct, remapProductResponse } from "../helpers"
import { MedusaError } from "@medusajs/utils"
import { HttpTypes } from "@medusajs/types"
import { refetchEntity } from "../../../utils/refetch-entity"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const variables = { id: req.params.id }
const selectFields = remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
const queryObject = remoteQueryObjectFromString({
entryPoint: "product",
variables,
fields: selectFields,
})
const product = await refetchEntity(
"product",
req.params.id,
req.scope,
selectFields
)
const [product] = await remoteQuery(queryObject)
if (!product) {
throw new MedusaError(MedusaError.Types.NOT_FOUND, "Product not found")
}
@@ -44,27 +31,29 @@ export const GET = async (
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminUpdateProductType>,
res: MedusaResponse
req: AuthenticatedMedusaRequest<HttpTypes.AdminUpdateProduct>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const { result } = await updateProductsWorkflow(req.scope).run({
input: {
selector: { id: req.params.id },
update: req.validatedBody as UpdateProductDTO,
update: req.validatedBody,
},
})
const product = await refetchProduct(
const product = await refetchEntity(
"product",
result[0].id,
req.scope,
req.remoteQueryConfig.fields
remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
)
res.status(200).json({ product: remapProductResponse(product) })
}
export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
res: MedusaResponse<HttpTypes.AdminProductDeleteResponse>
) => {
const id = req.params.id
@@ -8,44 +8,36 @@ import {
} from "@medusajs/core-flows"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import {
refetchProduct,
remapKeysForProduct,
remapKeysForVariant,
remapProductResponse,
remapVariantResponse,
} from "../../../helpers"
import { AdminUpdateProductVariantType } from "../../../validators"
import { HttpTypes } from "@medusajs/types"
import { refetchEntity } from "../../../../../utils/refetch-entity"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
res: MedusaResponse<HttpTypes.AdminProductVariantResponse>
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
// TODO: Should we allow fetching a variant without knowing the product ID? In such case we'll need to change the route to /admin/products/variants/:id
const productId = req.params.id
const variantId = req.params.variant_id
const variables = { id: variantId, product_id: productId }
const queryObject = remoteQueryObjectFromString({
entryPoint: "variant",
const variant = await refetchEntity(
"variant",
variables,
fields: remapKeysForVariant(req.remoteQueryConfig.fields ?? []),
})
req.scope,
remapKeysForVariant(req.remoteQueryConfig.fields ?? [])
)
const [variant] = await remoteQuery(queryObject)
res.status(200).json({ variant: remapVariantResponse(variant) })
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminUpdateProductVariantType>,
res: MedusaResponse
req: AuthenticatedMedusaRequest<HttpTypes.AdminUpdateProductVariant>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
// TODO: Should we allow fetching a variant without knowing the product ID? In such case we'll need to change the route to /admin/products/variants/:id
const productId = req.params.id
const variantId = req.params.variant_id
@@ -56,19 +48,19 @@ export const POST = async (
},
})
const product = await refetchProduct(
const product = await refetchEntity(
"product",
productId,
req.scope,
req.remoteQueryConfig.fields
remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
)
res.status(200).json({ product: remapProductResponse(product) })
}
export const DELETE = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
res: MedusaResponse<HttpTypes.AdminProductVariantDeleteResponse>
) => {
// TODO: Should we allow fetching a variant without knowing the product ID? In such case we'll need to change the route to /admin/products/variants/:id
const productId = req.params.id
const variantId = req.params.variant_id
@@ -77,16 +69,17 @@ export const DELETE = async (
input: { ids: [variantId] /* product_id: productId */ },
})
const product = await refetchProduct(
const product = await refetchEntity(
"product",
productId,
req.scope,
req.remoteQueryConfig.fields
remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
)
res.status(200).json({
id: variantId,
object: "variant",
deleted: true,
parent: product,
parent: remapProductResponse(product),
})
}
@@ -3,21 +3,12 @@ import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../../types/routing"
import {
AdminBatchUpdateProductVariantType,
AdminCreateProductType,
} from "../../../validators"
import { BatchMethodRequest } from "@medusajs/types"
import { refetchBatchVariants, remapVariantResponse } from "../../../helpers"
import { HttpTypes } from "@medusajs/types"
export const POST = async (
req: AuthenticatedMedusaRequest<
BatchMethodRequest<
AdminCreateProductType,
AdminBatchUpdateProductVariantType
>
>,
res: MedusaResponse
req: AuthenticatedMedusaRequest<HttpTypes.AdminBatchProductVariantRequest>,
res: MedusaResponse<HttpTypes.AdminBatchProductVariantResponse>
) => {
const productId = req.params.id
@@ -31,8 +22,7 @@ export const POST = async (
product_id: productId,
})),
delete: req.validatedBody.delete,
// TODO: Fix types
} as any
}
const { result } = await batchProductVariantsWorkflow(req.scope).run({
input: normalizedInput,
@@ -5,36 +5,29 @@ import {
import { createProductVariantsWorkflow } from "@medusajs/core-flows"
import {
remoteQueryObjectFromString,
ContainerRegistrationKeys,
} from "@medusajs/utils"
import {
refetchProduct,
remapKeysForProduct,
remapKeysForVariant,
remapProductResponse,
remapVariantResponse,
} from "../../helpers"
import { AdminCreateProductVariantType } from "../../validators"
import { HttpTypes } from "@medusajs/types"
import {
refetchEntities,
refetchEntity,
} from "../../../../utils/refetch-entity"
export const GET = async (
req: AuthenticatedMedusaRequest,
res: MedusaResponse
res: MedusaResponse<HttpTypes.AdminProductVariantListResponse>
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const productId = req.params.id
const queryObject = remoteQueryObjectFromString({
entryPoint: "variant",
variables: {
filters: { ...req.filterableFields, product_id: productId },
order: req.listConfig.order,
skip: req.listConfig.skip,
take: req.listConfig.take,
},
fields: remapKeysForVariant(req.remoteQueryConfig.fields ?? []),
})
const { rows: variants, metadata } = await remoteQuery(queryObject)
const { rows: variants, metadata } = await refetchEntities(
"variant",
{ ...req.filterableFields, product_id: productId },
req.scope,
remapKeysForVariant(req.remoteQueryConfig.fields ?? []),
req.remoteQueryConfig.pagination
)
res.json({
variants: variants.map(remapVariantResponse),
@@ -45,8 +38,8 @@ export const GET = async (
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminCreateProductVariantType>,
res: MedusaResponse
req: AuthenticatedMedusaRequest<HttpTypes.AdminCreateProductVariant>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const productId = req.params.id
const input = [
@@ -60,10 +53,12 @@ export const POST = async (
input: { product_variants: input },
})
const product = await refetchProduct(
const product = await refetchEntity(
"product",
productId,
req.scope,
req.remoteQueryConfig.fields
remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
)
res.status(200).json({ product: remapProductResponse(product) })
}
@@ -3,24 +3,15 @@ import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../types/routing"
import {
AdminBatchUpdateProductType,
AdminCreateProductType,
} from "../validators"
import { BatchMethodRequest } from "@medusajs/types"
import { refetchBatchProducts, remapProductResponse } from "../helpers"
import { HttpTypes } from "@medusajs/types"
export const POST = async (
req: AuthenticatedMedusaRequest<
BatchMethodRequest<AdminCreateProductType, AdminBatchUpdateProductType>
>,
res: MedusaResponse
req: AuthenticatedMedusaRequest<HttpTypes.AdminBatchProductRequest>,
res: MedusaResponse<HttpTypes.AdminBatchProductResponse>
) => {
// TODO: Fix types
const input = req.validatedBody as any
const { result } = await batchProductsWorkflow(req.scope).run({
input,
input: req.validatedBody,
})
const batchResults = await refetchBatchProducts(
@@ -4,6 +4,7 @@ import {
MedusaContainer,
ProductDTO,
ProductVariantDTO,
HttpTypes,
} from "@medusajs/types"
import {
ContainerRegistrationKeys,
@@ -46,19 +47,24 @@ export const remapKeysForVariant = (selectFields: string[]) => {
return [...variantFields, ...pricingFields]
}
export const remapProductResponse = (product: ProductDTO) => {
export const remapProductResponse = (
product: ProductDTO
): HttpTypes.AdminProduct => {
return {
...product,
variants: product.variants?.map(remapVariantResponse),
}
// TODO: Remove any once all typings are cleaned up
} as any
}
export const remapVariantResponse = (variant: ProductVariantDTO) => {
export const remapVariantResponse = (
variant: ProductVariantDTO
): HttpTypes.AdminProductVariant => {
if (!variant) {
return variant
}
return {
const resp = {
...variant,
prices: (variant as any).price_set?.prices?.map((price) => ({
id: price.id,
@@ -70,26 +76,11 @@ export const remapVariantResponse = (variant: ProductVariantDTO) => {
created_at: price.created_at,
updated_at: price.updated_at,
})),
price_set: undefined,
}
}
export const refetchProduct = async (
productId: string,
scope: MedusaContainer,
fields: string[]
) => {
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const queryObject = remoteQueryObjectFromString({
entryPoint: "product",
variables: {
filters: { id: productId },
},
fields: remapKeysForProduct(fields ?? []),
})
const products = await remoteQuery(queryObject)
return products[0]
delete (resp as any).price_set
// TODO: Remove any once all typings are cleaned up
return resp as any
}
export const refetchVariant = async (
+18 -34
View File
@@ -1,40 +1,25 @@
import { createProductsWorkflow } from "@medusajs/core-flows"
import { CreateProductDTO } from "@medusajs/types"
import {
ContainerRegistrationKeys,
remoteQueryObjectFromString,
} from "@medusajs/utils"
import { HttpTypes } from "@medusajs/types"
import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../types/routing"
import {
refetchProduct,
remapKeysForProduct,
remapProductResponse,
} from "./helpers"
import {
AdminCreateProductType,
AdminGetProductsParamsType,
} from "./validators"
import { remapKeysForProduct, remapProductResponse } from "./helpers"
import { refetchEntities, refetchEntity } from "../../utils/refetch-entity"
export const GET = async (
req: AuthenticatedMedusaRequest<AdminGetProductsParamsType>,
res: MedusaResponse
req: AuthenticatedMedusaRequest<HttpTypes.AdminProductParams>,
res: MedusaResponse<HttpTypes.AdminProductListResponse>
) => {
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
const selectFields = remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
const queryObject = remoteQueryObjectFromString({
entryPoint: "product",
variables: {
filters: req.filterableFields,
...req.remoteQueryConfig.pagination,
},
fields: selectFields,
})
const { rows: products, metadata } = await remoteQuery(queryObject)
const { rows: products, metadata } = await refetchEntities(
"product",
req.filterableFields,
req.scope,
selectFields,
req.remoteQueryConfig.pagination
)
res.json({
products: products.map(remapProductResponse),
@@ -45,19 +30,18 @@ export const GET = async (
}
export const POST = async (
req: AuthenticatedMedusaRequest<AdminCreateProductType>,
res: MedusaResponse
req: AuthenticatedMedusaRequest<HttpTypes.AdminCreateProduct>,
res: MedusaResponse<HttpTypes.AdminProductResponse>
) => {
const input = [req.validatedBody as CreateProductDTO]
const { result } = await createProductsWorkflow(req.scope).run({
input: { products: input },
input: { products: [req.validatedBody] },
})
const product = await refetchProduct(
const product = await refetchEntity(
"product",
result[0].id,
req.scope,
req.remoteQueryConfig.fields
remapKeysForProduct(req.remoteQueryConfig.fields ?? [])
)
res.status(200).json({ product: remapProductResponse(product) })
@@ -4,11 +4,11 @@ import {
} from "@medusajs/utils"
import { NextFunction } from "express"
import { MedusaRequest } from "../../../../types/routing"
import { AdminGetProductsParamsType } from "../validators"
import { HttpTypes } from "@medusajs/types"
export function maybeApplyPriceListsFilter() {
return async (req: MedusaRequest, _, next: NextFunction) => {
const filterableFields: AdminGetProductsParamsType = req.filterableFields
const filterableFields: HttpTypes.AdminProductParams = req.filterableFields
if (!filterableFields.price_list_id) {
return next()
@@ -163,13 +163,30 @@ export const AdminCreateProductVariant = z
export type AdminUpdateProductVariantType = z.infer<
typeof AdminUpdateProductVariant
>
export const AdminUpdateProductVariant = AdminCreateProductVariant.extend({
id: z.string().optional(),
title: z.string().optional(),
prices: z.array(AdminUpdateVariantPrice).optional(),
allow_backorder: z.boolean().optional(),
manage_inventory: z.boolean().optional(),
}).strict()
export const AdminUpdateProductVariant = z
.object({
id: z.string().optional(),
title: z.string().optional(),
prices: z.array(AdminUpdateVariantPrice).optional(),
sku: z.string().nullable().optional().nullable(),
ean: z.string().nullable().optional().nullable(),
upc: z.string().nullable().optional().nullable(),
barcode: z.string().nullable().optional().nullable(),
hs_code: z.string().nullable().optional().nullable(),
mid_code: z.string().nullable().optional().nullable(),
allow_backorder: z.boolean().optional(),
manage_inventory: z.boolean().optional(),
variant_rank: z.number().optional(),
weight: z.number().nullable().optional().nullable(),
length: z.number().nullable().optional().nullable(),
height: z.number().nullable().optional().nullable(),
width: z.number().nullable().optional().nullable(),
origin_country: z.string().nullable().optional().nullable(),
material: z.string().nullable().optional().nullable(),
metadata: z.record(z.unknown()).optional().nullable(),
options: z.record(z.string()).optional(),
})
.strict()
export type AdminBatchUpdateProductVariantType = z.infer<
typeof AdminBatchUpdateProductVariant
@@ -190,46 +207,57 @@ export const AdminCreateProduct = z
description: z.string().nullable().optional(),
is_giftcard: z.boolean().optional().default(false),
discountable: z.boolean().optional().default(true),
images: z
.array(z.object({ url: z.string() }))
.nullable()
.optional(),
thumbnail: z.string().nullable().optional(),
images: z.array(z.object({ url: z.string() })).optional(),
thumbnail: z.string().optional(),
handle: z.string().optional(),
status: statusEnum.optional().default(ProductStatus.DRAFT),
type_id: z.string().nullable().optional(),
collection_id: z.string().nullable().optional(),
categories: z
.array(AdminCreateProductProductCategory)
.nullable()
.optional(),
tags: z.array(AdminUpdateProductTag).nullable().optional(),
type_id: z.string().optional(),
collection_id: z.string().optional(),
categories: z.array(AdminCreateProductProductCategory).optional(),
tags: z.array(AdminUpdateProductTag).optional(),
options: z.array(AdminCreateProductOption).optional(),
variants: z.array(AdminCreateProductVariant).optional(),
sales_channels: z
.array(z.object({ id: z.string() }))
.nullable()
.optional(),
weight: z.number().nullable().optional(),
length: z.number().nullable().optional(),
height: z.number().nullable().optional(),
width: z.number().nullable().optional(),
hs_code: z.string().nullable().optional(),
mid_code: z.string().nullable().optional(),
origin_country: z.string().nullable().optional(),
material: z.string().nullable().optional(),
sales_channels: z.array(z.object({ id: z.string() })).optional(),
weight: z.number().optional(),
length: z.number().optional(),
height: z.number().optional(),
width: z.number().optional(),
hs_code: z.string().optional(),
mid_code: z.string().optional(),
origin_country: z.string().optional(),
material: z.string().optional(),
metadata: z.record(z.unknown()).optional(),
})
.strict()
export type AdminUpdateProductType = z.infer<typeof AdminUpdateProduct>
export const AdminUpdateProduct = AdminCreateProduct.omit({ is_giftcard: true })
.extend({
export const AdminUpdateProduct = 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(),
status: statusEnum.optional(),
subtitle: z.string().optional().nullable(),
description: z.string().optional().nullable(),
images: z.array(z.object({ url: z.string() })).optional(),
thumbnail: z.string().optional().nullable(),
handle: z.string().optional(),
type_id: z.string().optional().nullable(),
collection_id: z.string().optional().nullable(),
categories: z.array(AdminCreateProductProductCategory).optional(),
tags: z.array(AdminUpdateProductTag).nullable().optional(),
sales_channels: z.array(z.object({ id: z.string() })).optional(),
weight: z.number().optional().nullable(),
length: z.number().optional().nullable(),
height: z.number().optional().nullable(),
width: z.number().optional().nullable(),
hs_code: z.string().optional().nullable(),
mid_code: z.string().optional().nullable(),
origin_country: z.string().optional().nullable(),
material: z.string().optional().nullable(),
metadata: z.record(z.unknown()).optional().nullable(),
})
.strict()