feat(medusa,types): added store apis for products (#7144)
what: - adds products list api - adds products retrieve api RESOLVES CORE-2016
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { ProductStatus } from "@medusajs/utils"
|
||||
import { z } from "zod"
|
||||
import { optionalBooleanMapper } from "../../../utils/validators/is-boolean"
|
||||
import { GetProductsParams } from "../../utils/common-validators"
|
||||
import {
|
||||
createFindParams,
|
||||
createOperatorMap,
|
||||
@@ -13,50 +13,6 @@ export const AdminGetProductParams = createSelectParams()
|
||||
export const AdminGetProductVariantParams = createSelectParams()
|
||||
export const AdminGetProductOptionParams = createSelectParams()
|
||||
|
||||
export type AdminGetProductsParamsType = z.infer<typeof AdminGetProductsParams>
|
||||
export const AdminGetProductsParams = createFindParams({
|
||||
offset: 0,
|
||||
limit: 50,
|
||||
}).merge(
|
||||
z.object({
|
||||
q: z.string().optional(),
|
||||
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
status: statusEnum.array().optional(),
|
||||
title: z.string().optional(),
|
||||
handle: z.string().optional(),
|
||||
is_giftcard: z.preprocess(
|
||||
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||
z.boolean().optional()
|
||||
),
|
||||
category_id: z.string().array().optional(),
|
||||
price_list_id: z.string().array().optional(),
|
||||
sales_channel_id: z.string().array().optional(),
|
||||
collection_id: z.string().array().optional(),
|
||||
tags: z.string().array().optional(),
|
||||
type_id: z.string().array().optional(),
|
||||
// TODO: Replace this with AdminGetProductVariantsParams when its available
|
||||
variants: z.record(z.unknown()).optional(),
|
||||
created_at: createOperatorMap().optional(),
|
||||
updated_at: createOperatorMap().optional(),
|
||||
deleted_at: createOperatorMap().optional(),
|
||||
$and: z.lazy(() => AdminGetProductsParams.array()).optional(),
|
||||
$or: z.lazy(() => AdminGetProductsParams.array()).optional(),
|
||||
})
|
||||
)
|
||||
// TODO: These were part of the products find query, add them once supported
|
||||
// @IsString()
|
||||
// @IsOptional()
|
||||
// discount_condition_id?: string
|
||||
|
||||
// @IsArray()
|
||||
// @IsOptional()
|
||||
// category_id?: string[]
|
||||
|
||||
// @IsBoolean()
|
||||
// @IsOptional()
|
||||
// @Transform(({ value }) => optionalBooleanMapper.get(value.toLowerCase()))
|
||||
// include_category_children?: boolean
|
||||
|
||||
export type AdminGetProductVariantsParamsType = z.infer<
|
||||
typeof AdminGetProductVariantsParams
|
||||
>
|
||||
@@ -77,6 +33,21 @@ export const AdminGetProductVariantsParams = createFindParams({
|
||||
})
|
||||
)
|
||||
|
||||
export type AdminGetProductsParamsType = z.infer<typeof AdminGetProductsParams>
|
||||
export const AdminGetProductsParams = createFindParams({
|
||||
offset: 0,
|
||||
limit: 50,
|
||||
}).merge(
|
||||
z
|
||||
.object({
|
||||
variants: AdminGetProductVariantsParams.optional(),
|
||||
price_list_id: z.string().array().optional(),
|
||||
$and: z.lazy(() => AdminGetProductsParams.array()).optional(),
|
||||
$or: z.lazy(() => AdminGetProductsParams.array()).optional(),
|
||||
})
|
||||
.merge(GetProductsParams)
|
||||
)
|
||||
|
||||
export type AdminGetProductOptionsParamsType = z.infer<
|
||||
typeof AdminGetProductOptionsParams
|
||||
>
|
||||
|
||||
@@ -36,6 +36,7 @@ import { hooksRoutesMiddlewares } from "./hooks/middlewares"
|
||||
import { storeCartRoutesMiddlewares } from "./store/carts/middlewares"
|
||||
import { storeCurrencyRoutesMiddlewares } from "./store/currencies/middlewares"
|
||||
import { storeCustomerRoutesMiddlewares } from "./store/customers/middlewares"
|
||||
import { storeProductRoutesMiddlewares } from "./store/products/middlewares"
|
||||
import { storeRegionRoutesMiddlewares } from "./store/regions/middlewares"
|
||||
|
||||
export const config: MiddlewaresConfig = {
|
||||
@@ -80,5 +81,6 @@ export const config: MiddlewaresConfig = {
|
||||
...adminShippingProfilesMiddlewares,
|
||||
...adminFulfillmentsRoutesMiddlewares,
|
||||
...adminFulfillmentProvidersRoutesMiddlewares,
|
||||
...storeProductRoutesMiddlewares,
|
||||
],
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { isPresent } from "@medusajs/utils"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
|
||||
import { refetchProduct } from "../helpers"
|
||||
import { StoreGetProductsParamsType } from "../validators"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest<StoreGetProductsParamsType>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const context = isPresent(req.pricingContext)
|
||||
? {
|
||||
"variants.calculated_price": { context: req.pricingContext },
|
||||
}
|
||||
: undefined
|
||||
|
||||
const product = await refetchProduct(
|
||||
{
|
||||
id: req.params.id,
|
||||
context,
|
||||
},
|
||||
req.scope,
|
||||
req.remoteQueryConfig.fields
|
||||
)
|
||||
|
||||
res.json({ product })
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import { isPresent } from "@medusajs/utils"
|
||||
import { refetchEntity } from "../../utils/refetch-entity"
|
||||
import { StoreGetProductsParamsType } from "./validators"
|
||||
|
||||
// For category filters, we only allow showcasing public and active categories
|
||||
// TODO: This should ideally be done in the middleware, write a generic filter to conditionally
|
||||
// map these values or normalize the filters to the ones expected by remote query
|
||||
export function wrapWithCategoryFilters(filters: StoreGetProductsParamsType) {
|
||||
const categoriesFilter = isPresent(filters.category_id)
|
||||
? {
|
||||
categories: {
|
||||
...filters.category_id,
|
||||
is_internal: false,
|
||||
is_active: true,
|
||||
},
|
||||
}
|
||||
: {}
|
||||
|
||||
delete filters.category_id
|
||||
|
||||
return {
|
||||
...filters,
|
||||
...categoriesFilter,
|
||||
}
|
||||
}
|
||||
|
||||
export const refetchProduct = async (
|
||||
idOrFilter: string | object,
|
||||
scope: MedusaContainer,
|
||||
fields: string[]
|
||||
) => {
|
||||
return await refetchEntity("product", idOrFilter, scope, fields)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import { ProductStatus } from "@medusajs/utils"
|
||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||
import { maybeApplyLinkFilter } from "../../utils/maybe-apply-link-filter"
|
||||
import {
|
||||
applyDefaultFilters,
|
||||
filterByValidSalesChannels,
|
||||
setPricingContext,
|
||||
} from "../../utils/middlewares"
|
||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||
import * as QueryConfig from "./query-config"
|
||||
import {
|
||||
StoreGetProductsParams,
|
||||
StoreGetProductsParamsType,
|
||||
} from "./validators"
|
||||
|
||||
export const storeProductRoutesMiddlewares: MiddlewareRoute[] = [
|
||||
{
|
||||
method: "ALL",
|
||||
matcher: "/store/products*",
|
||||
middlewares: [
|
||||
authenticate("store", ["session", "bearer"], {
|
||||
allowUnauthenticated: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/store/products",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
StoreGetProductsParams,
|
||||
QueryConfig.listProductQueryConfig
|
||||
),
|
||||
filterByValidSalesChannels(),
|
||||
maybeApplyLinkFilter({
|
||||
entryPoint: "product_sales_channel",
|
||||
resourceId: "product_id",
|
||||
filterableField: "sales_channel_id",
|
||||
}),
|
||||
applyDefaultFilters<StoreGetProductsParamsType>({
|
||||
status: ProductStatus.PUBLISHED,
|
||||
}),
|
||||
setPricingContext(),
|
||||
],
|
||||
},
|
||||
{
|
||||
method: ["GET"],
|
||||
matcher: "/store/products/:id",
|
||||
middlewares: [
|
||||
validateAndTransformQuery(
|
||||
StoreGetProductsParams,
|
||||
QueryConfig.retrieveProductQueryConfig
|
||||
),
|
||||
filterByValidSalesChannels(),
|
||||
maybeApplyLinkFilter({
|
||||
entryPoint: "product_sales_channel",
|
||||
resourceId: "product_id",
|
||||
filterableField: "sales_channel_id",
|
||||
}),
|
||||
applyDefaultFilters<StoreGetProductsParamsType>({
|
||||
status: ProductStatus.PUBLISHED,
|
||||
}),
|
||||
setPricingContext(),
|
||||
],
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,41 @@
|
||||
export const defaultStoreProductFields = [
|
||||
"id",
|
||||
"title",
|
||||
"subtitle",
|
||||
"description",
|
||||
"handle",
|
||||
"is_giftcard",
|
||||
"discountable",
|
||||
"thumbnail",
|
||||
"collection_id",
|
||||
"type_id",
|
||||
"weight",
|
||||
"length",
|
||||
"height",
|
||||
"width",
|
||||
"hs_code",
|
||||
"origin_country",
|
||||
"mid_code",
|
||||
"material",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"*type",
|
||||
"*collection",
|
||||
"*options",
|
||||
"*options.values",
|
||||
"*tags",
|
||||
"*images",
|
||||
"*variants",
|
||||
"*variants.options",
|
||||
]
|
||||
|
||||
export const retrieveProductQueryConfig = {
|
||||
defaults: defaultStoreProductFields,
|
||||
isList: false,
|
||||
}
|
||||
|
||||
export const listProductQueryConfig = {
|
||||
...retrieveProductQueryConfig,
|
||||
defaultLimit: 50,
|
||||
isList: true,
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
isPresent,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
import { MedusaRequest, MedusaResponse } from "../../../types/routing"
|
||||
import { wrapWithCategoryFilters } from "./helpers"
|
||||
import { StoreGetProductsParamsType } from "./validators"
|
||||
|
||||
export const GET = async (
|
||||
req: MedusaRequest<StoreGetProductsParamsType>,
|
||||
res: MedusaResponse
|
||||
) => {
|
||||
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
const context = isPresent(req.pricingContext)
|
||||
? {
|
||||
"variants.calculated_price": { context: req.pricingContext },
|
||||
}
|
||||
: undefined
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint: "product",
|
||||
variables: {
|
||||
filters: wrapWithCategoryFilters(req.filterableFields),
|
||||
...context,
|
||||
...req.remoteQueryConfig.pagination,
|
||||
},
|
||||
fields: req.remoteQueryConfig.fields,
|
||||
})
|
||||
|
||||
const { rows: products, metadata } = await remoteQuery(queryObject)
|
||||
|
||||
res.json({
|
||||
products,
|
||||
count: metadata.count,
|
||||
offset: metadata.skip,
|
||||
limit: metadata.take,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { z } from "zod"
|
||||
import {
|
||||
GetProductsParams,
|
||||
ProductStatusEnum,
|
||||
} from "../../utils/common-validators"
|
||||
import {
|
||||
createFindParams,
|
||||
createOperatorMap,
|
||||
createSelectParams,
|
||||
} from "../../utils/validators"
|
||||
|
||||
export const StoreGetProductParams = createSelectParams()
|
||||
|
||||
export type StoreGetProductVariantsParamsType = z.infer<
|
||||
typeof StoreGetProductVariantsParams
|
||||
>
|
||||
export const StoreGetProductVariantsParams = createFindParams({
|
||||
offset: 0,
|
||||
limit: 50,
|
||||
}).merge(
|
||||
z.object({
|
||||
q: z.string().optional(),
|
||||
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
status: ProductStatusEnum.array().optional(),
|
||||
created_at: createOperatorMap().optional(),
|
||||
updated_at: createOperatorMap().optional(),
|
||||
deleted_at: createOperatorMap().optional(),
|
||||
$and: z.lazy(() => StoreGetProductsParams.array()).optional(),
|
||||
$or: z.lazy(() => StoreGetProductsParams.array()).optional(),
|
||||
})
|
||||
)
|
||||
|
||||
export type StoreGetProductsParamsType = z.infer<typeof StoreGetProductsParams>
|
||||
export const StoreGetProductsParams = createFindParams({
|
||||
offset: 0,
|
||||
limit: 50,
|
||||
}).merge(
|
||||
z
|
||||
.object({
|
||||
region_id: z.string().optional(),
|
||||
currency_code: z.string().optional(),
|
||||
variants: StoreGetProductVariantsParams.optional(),
|
||||
$and: z.lazy(() => StoreGetProductsParams.array()).optional(),
|
||||
$or: z.lazy(() => StoreGetProductsParams.array()).optional(),
|
||||
})
|
||||
.merge(GetProductsParams)
|
||||
.strict()
|
||||
)
|
||||
+6
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod"
|
||||
import { optionalBooleanMapper } from "../../../utils/validators/is-boolean"
|
||||
|
||||
export const AddressPayload = z
|
||||
.object({
|
||||
@@ -24,3 +25,8 @@ export const BigNumberInput = z.union([
|
||||
precision: z.number(),
|
||||
}),
|
||||
])
|
||||
|
||||
export const OptionalBooleanValidator = z.preprocess(
|
||||
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||
z.boolean().optional()
|
||||
)
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./common"
|
||||
export * from "./products"
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ProductStatus } from "@medusajs/utils"
|
||||
import { z } from "zod"
|
||||
import { createOperatorMap } from "../../validators"
|
||||
import { OptionalBooleanValidator } from "../common"
|
||||
|
||||
export const ProductStatusEnum = z.nativeEnum(ProductStatus)
|
||||
|
||||
export const GetProductsParams = z.object({
|
||||
q: z.string().optional(),
|
||||
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
title: z.string().optional(),
|
||||
handle: z.string().optional(),
|
||||
is_giftcard: OptionalBooleanValidator,
|
||||
category_id: z.string().array().optional(),
|
||||
sales_channel_id: z.string().array().optional(),
|
||||
collection_id: z.string().array().optional(),
|
||||
tags: z.string().array().optional(),
|
||||
type_id: z.string().array().optional(),
|
||||
created_at: createOperatorMap().optional(),
|
||||
updated_at: createOperatorMap().optional(),
|
||||
deleted_at: createOperatorMap().optional(),
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
import { isObject } from "@medusajs/utils"
|
||||
import { NextFunction } from "express"
|
||||
import { MedusaRequest } from "../../../../types/routing"
|
||||
|
||||
export function applyDefaultFilters<TFilter extends object>(filters: TFilter) {
|
||||
return async (req: MedusaRequest, _, next: NextFunction) => {
|
||||
const filterableFields = req.filterableFields || {}
|
||||
|
||||
for (const [filter, filterValue] of Object.entries(filters)) {
|
||||
let existingFilter = filterableFields[filter]
|
||||
|
||||
if (existingFilter && isObject(existingFilter)) {
|
||||
// If an existing filter is found, append to it
|
||||
filterableFields[filter] = {
|
||||
...existingFilter,
|
||||
[filter]: filterValue,
|
||||
}
|
||||
} else {
|
||||
filterableFields[filter] = filterValue
|
||||
}
|
||||
}
|
||||
|
||||
req.filterableFields = filterableFields
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./apply-default-filters"
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./common"
|
||||
export * from "./products"
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
import { isPresent, MedusaError } from "@medusajs/utils"
|
||||
import { NextFunction } from "express"
|
||||
import { AuthenticatedMedusaRequest } from "../../../../types/routing"
|
||||
import { refetchEntity } from "../../refetch-entity"
|
||||
|
||||
export function filterByValidSalesChannels() {
|
||||
return async (req: AuthenticatedMedusaRequest, _, next: NextFunction) => {
|
||||
const publishableApiKey = req.get("x-publishable-api-key")
|
||||
const salesChannelIds = req.filterableFields.sales_channel_id as
|
||||
| string[]
|
||||
| undefined
|
||||
|
||||
const store = await refetchEntity("stores", {}, req.scope, [
|
||||
"default_sales_channel_id",
|
||||
])
|
||||
|
||||
if (!store) {
|
||||
try {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, `Store not found`)
|
||||
} catch (e) {
|
||||
return next(e)
|
||||
}
|
||||
}
|
||||
// Always set sales channels in the following priority
|
||||
// - any existing sales chennel ids passed through query params
|
||||
// - if none, we set the default sales channel
|
||||
req.filterableFields.sales_channel_id = salesChannelIds ?? [
|
||||
store.default_sales_channel_id,
|
||||
]
|
||||
|
||||
// Return early if no publishable keys are found
|
||||
if (!isPresent(publishableApiKey)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
// When publishable keys are present, we fetch for all sales chennels attached
|
||||
// to the publishable key and validate the sales channel filter against it
|
||||
const apiKey = await refetchEntity(
|
||||
"api_key",
|
||||
{ token: publishableApiKey },
|
||||
req.scope,
|
||||
["id", "sales_channels_link.sales_channel_id"]
|
||||
)
|
||||
|
||||
if (!apiKey) {
|
||||
try {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Publishable API key not found`
|
||||
)
|
||||
} catch (e) {
|
||||
return next(e)
|
||||
}
|
||||
}
|
||||
|
||||
const validSalesChannelIds = apiKey.sales_channels_link.map(
|
||||
(link) => link.sales_channel_id
|
||||
)
|
||||
|
||||
const invalidSalesChannelIds = (salesChannelIds || []).filter(
|
||||
(id) => !validSalesChannelIds.includes(id)
|
||||
)
|
||||
|
||||
if (invalidSalesChannelIds.length) {
|
||||
try {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Invalid sales channel filters provided - ${invalidSalesChannelIds.join(
|
||||
", "
|
||||
)}`
|
||||
)
|
||||
} catch (e) {
|
||||
return next(e)
|
||||
}
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./filter-by-valid-sales-channels"
|
||||
export * from "./set-pricing-context"
|
||||
@@ -0,0 +1,89 @@
|
||||
import { MedusaPricingContext } from "@medusajs/types"
|
||||
import { isPresent, MedusaError } from "@medusajs/utils"
|
||||
import { NextFunction } from "express"
|
||||
import { AuthenticatedMedusaRequest } from "../../../../types/routing"
|
||||
import { refetchEntities, refetchEntity } from "../../refetch-entity"
|
||||
|
||||
export function setPricingContext() {
|
||||
return async (req: AuthenticatedMedusaRequest, _, next: NextFunction) => {
|
||||
// If the endpoint doesn't request prices, we can exit early
|
||||
if (
|
||||
!req.remoteQueryConfig.fields.some((field) =>
|
||||
field.startsWith("variants.calculated_price")
|
||||
)
|
||||
) {
|
||||
delete req.filterableFields.region_id
|
||||
delete req.filterableFields.currency_code
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
const query = req.filterableFields || {}
|
||||
const pricingContext: MedusaPricingContext = {}
|
||||
const customerId = req.user?.customer_id
|
||||
|
||||
if (query.region_id) {
|
||||
const region = await refetchEntity("region", query.region_id, req.scope, [
|
||||
"id",
|
||||
"currency_code",
|
||||
])
|
||||
|
||||
if (region) {
|
||||
pricingContext.region_id = region.id
|
||||
}
|
||||
|
||||
if (region?.currency_code) {
|
||||
pricingContext.currency_code = region.currency_code
|
||||
}
|
||||
|
||||
delete req.filterableFields.region_id
|
||||
}
|
||||
|
||||
// If a currency code is explicitly passed, we should be using that instead of the
|
||||
// regions currency code
|
||||
if (query.currency_code) {
|
||||
const currency = await refetchEntity(
|
||||
"currency",
|
||||
{ code: query.currency_code },
|
||||
req.scope,
|
||||
["code"]
|
||||
)
|
||||
|
||||
if (currency) {
|
||||
pricingContext.currency_code = currency.code
|
||||
}
|
||||
|
||||
delete req.filterableFields.currency_code
|
||||
}
|
||||
|
||||
// Find all the customer groups the customer is a part of and set
|
||||
if (customerId) {
|
||||
const customerGroups = await refetchEntities(
|
||||
"customer_group",
|
||||
{ customer_id: customerId },
|
||||
req.scope,
|
||||
["id"]
|
||||
)
|
||||
|
||||
pricingContext.customer_group_id = customerGroups.map((cg) => cg.id)
|
||||
|
||||
delete req.filterableFields.customer_id
|
||||
}
|
||||
|
||||
// If a region or currency_code is not present in the context, we will not be able to calculate prices
|
||||
if (!isPresent(pricingContext)) {
|
||||
try {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Pricing parameters (currency_code or region_id) are required to calculate prices`
|
||||
)
|
||||
} catch (e) {
|
||||
return next(e)
|
||||
}
|
||||
}
|
||||
|
||||
req.pricingContext = pricingContext
|
||||
|
||||
return next()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
import { MedusaContainer } from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
isString,
|
||||
remoteQueryObjectFromString,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
export const refetchEntities = async (
|
||||
entryPoint: string,
|
||||
idOrFilter: string | object,
|
||||
scope: MedusaContainer,
|
||||
fields: string[]
|
||||
) => {
|
||||
const remoteQuery = scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||
const filters = isString(idOrFilter) ? { id: idOrFilter } : idOrFilter
|
||||
let context: object = {}
|
||||
|
||||
if ("context" in filters) {
|
||||
if (filters.context) {
|
||||
context = filters.context!
|
||||
}
|
||||
|
||||
delete filters.context
|
||||
}
|
||||
|
||||
let variables = { filters, ...context }
|
||||
|
||||
const queryObject = remoteQueryObjectFromString({
|
||||
entryPoint,
|
||||
variables,
|
||||
fields,
|
||||
})
|
||||
|
||||
return await remoteQuery(queryObject)
|
||||
}
|
||||
|
||||
export const refetchEntity = async (
|
||||
entryPoint: string,
|
||||
idOrFilter: string | object,
|
||||
scope: MedusaContainer,
|
||||
fields: string[]
|
||||
) => {
|
||||
const [entity] = await refetchEntities(entryPoint, idOrFilter, scope, fields)
|
||||
|
||||
return entity
|
||||
}
|
||||
@@ -44,7 +44,6 @@ export function validateAndTransformQuery<TEntity extends BaseEntity>(
|
||||
return async (req: MedusaRequest, _: MedusaResponse, next: NextFunction) => {
|
||||
try {
|
||||
const query = normalizeQuery(req)
|
||||
|
||||
const validated = await zodValidator(zodSchema, query)
|
||||
const cnf = queryConfig.isList
|
||||
? prepareListQuery(validated, queryConfig)
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { NextFunction, Request, Response } from "express"
|
||||
import type { Customer, User } from "../models"
|
||||
|
||||
import { MedusaContainer, RequestQueryFields } from "@medusajs/types"
|
||||
import { FindConfig } from "./common"
|
||||
import {
|
||||
MedusaContainer,
|
||||
MedusaPricingContext,
|
||||
RequestQueryFields,
|
||||
} from "@medusajs/types"
|
||||
import * as core from "express-serve-static-core"
|
||||
import { FindConfig } from "./common"
|
||||
|
||||
export interface MedusaRequest<Body = unknown>
|
||||
extends Request<core.ParamsDictionary, any, Body> {
|
||||
@@ -50,6 +54,10 @@ export interface MedusaRequest<Body = unknown>
|
||||
session?: any
|
||||
rawBody?: any
|
||||
requestId?: string
|
||||
/**
|
||||
* An object that carries the context that is used to calculate prices for variants
|
||||
*/
|
||||
pricingContext?: MedusaPricingContext
|
||||
}
|
||||
|
||||
export interface AuthenticatedMedusaRequest<Body = never>
|
||||
|
||||
@@ -4,4 +4,5 @@ export * from "./price-list"
|
||||
export * from "./price-rule"
|
||||
export * from "./price-set"
|
||||
export * from "./price-set-rule-type"
|
||||
export * from "./pricing-context"
|
||||
export * from "./rule-type"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export type MedusaPricingContext = {
|
||||
region_id?: string
|
||||
currency_code?: string
|
||||
customer_id?: string
|
||||
customer_group_id?: string[]
|
||||
}
|
||||
Reference in New Issue
Block a user