fix(medusa): fields params usage in the storefront endpoints (#2980)
This commit is contained in:
@@ -39,6 +39,20 @@ export function transformQuery<
|
||||
])
|
||||
req.filterableFields = removeUndefinedProperties(req.filterableFields)
|
||||
|
||||
if (
|
||||
(queryConfig?.defaultFields || validated.fields) &&
|
||||
(queryConfig?.defaultRelations || validated.expand)
|
||||
) {
|
||||
req.allowedProperties = [
|
||||
...(validated.fields
|
||||
? validated.fields.split(",")
|
||||
: queryConfig?.allowedFields || [])!,
|
||||
...(validated.expand
|
||||
? validated.expand.split(",")
|
||||
: queryConfig?.allowedRelations || [])!,
|
||||
] as unknown as string[]
|
||||
}
|
||||
|
||||
if (queryConfig?.isList) {
|
||||
req.listConfig = prepareListQuery(
|
||||
validated,
|
||||
|
||||
@@ -6,7 +6,7 @@ import GiftCardService from "../../../../services/gift-card"
|
||||
* @oas [get] /gift-cards/{code}
|
||||
* operationId: "GetGiftCardsCode"
|
||||
* summary: "Get Gift Card by Code"
|
||||
* description: "Retrieves a Gift Card by its associated unqiue code."
|
||||
* description: "Retrieves a Gift Card by its associated unique code."
|
||||
* parameters:
|
||||
* - (path) code=* {string} The unique Gift Card code.
|
||||
* x-codegen:
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { defaultStoreOrdersFields, defaultStoreOrdersRelations } from "./index"
|
||||
|
||||
import { OrderService } from "../../../../services"
|
||||
import { FindParams } from "../../../../types/common"
|
||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||
|
||||
/**
|
||||
* @oas [get] /orders/{id}
|
||||
@@ -9,6 +11,8 @@ import { OrderService } from "../../../../services"
|
||||
* description: "Retrieves an Order"
|
||||
* parameters:
|
||||
* - (path) id=* {string} The id of the Order.
|
||||
* - (query) fields {string} (Comma separated) Which fields should be included in the result.
|
||||
* - (query) expand {string} (Comma separated) Which fields should be expanded in the result.
|
||||
* x-codegen:
|
||||
* method: retrieve
|
||||
* x-codeSamples:
|
||||
@@ -54,5 +58,9 @@ export default async (req, res) => {
|
||||
relations: defaultStoreOrdersRelations,
|
||||
})
|
||||
|
||||
res.json({ order })
|
||||
res.json({
|
||||
order: cleanResponseData(order, req.allowedProperties || []),
|
||||
})
|
||||
}
|
||||
|
||||
export class StoreGetOrderParams extends FindParams {}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { Router } from "express"
|
||||
import "reflect-metadata"
|
||||
import { Order } from "../../../.."
|
||||
import middlewares, { transformBody } from "../../../middlewares"
|
||||
import middlewares, {
|
||||
transformBody,
|
||||
transformQuery,
|
||||
} from "../../../middlewares"
|
||||
import requireCustomerAuthentication from "../../../middlewares/require-customer-authentication"
|
||||
import { StorePostCustomersCustomerOrderClaimReq } from "./request-order"
|
||||
import { StorePostCustomersCustomerAcceptClaimReq } from "./confirm-order-request"
|
||||
import { StoreGetOrderParams } from "./get-order"
|
||||
import { StoreGetOrdersParams } from "./lookup-order"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -14,12 +19,31 @@ export default (app) => {
|
||||
/**
|
||||
* Lookup
|
||||
*/
|
||||
route.get("/", middlewares.wrap(require("./lookup-order").default))
|
||||
route.get(
|
||||
"/",
|
||||
transformQuery(StoreGetOrdersParams, {
|
||||
defaultFields: defaultStoreOrdersFields,
|
||||
defaultRelations: defaultStoreOrdersRelations,
|
||||
allowedFields: allowedStoreOrdersFields,
|
||||
allowedRelations: allowedStoreOrdersRelations,
|
||||
isList: true,
|
||||
}),
|
||||
middlewares.wrap(require("./lookup-order").default)
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve Order
|
||||
*/
|
||||
route.get("/:id", middlewares.wrap(require("./get-order").default))
|
||||
route.get(
|
||||
"/:id",
|
||||
transformQuery(StoreGetOrderParams, {
|
||||
defaultFields: defaultStoreOrdersFields,
|
||||
defaultRelations: defaultStoreOrdersRelations,
|
||||
allowedFields: allowedStoreOrdersFields,
|
||||
allowedRelations: allowedStoreOrdersRelations,
|
||||
}),
|
||||
middlewares.wrap(require("./get-order").default)
|
||||
)
|
||||
|
||||
/**
|
||||
* Retrieve by Cart Id
|
||||
@@ -60,6 +84,11 @@ export const defaultStoreOrdersRelations = [
|
||||
"region",
|
||||
]
|
||||
|
||||
export const allowedStoreOrdersRelations = [
|
||||
...defaultStoreOrdersRelations,
|
||||
"billing_address",
|
||||
]
|
||||
|
||||
export const defaultStoreOrdersFields = [
|
||||
"id",
|
||||
"status",
|
||||
@@ -83,6 +112,21 @@ export const defaultStoreOrdersFields = [
|
||||
"total",
|
||||
] as (keyof Order)[]
|
||||
|
||||
export const allowedStoreOrdersFields = [
|
||||
...defaultStoreOrdersFields,
|
||||
"object",
|
||||
"shipping_total",
|
||||
"discount_total",
|
||||
"tax_total",
|
||||
"refunded_total",
|
||||
"total",
|
||||
"subtotal",
|
||||
"paid_total",
|
||||
"refundable_amount",
|
||||
"gift_card_total",
|
||||
"gift_card_tax_total",
|
||||
]
|
||||
|
||||
/**
|
||||
* @schema StoreOrdersRes
|
||||
* type: object
|
||||
|
||||
@@ -5,11 +5,13 @@ import {
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { defaultStoreOrdersFields, defaultStoreOrdersRelations } from "."
|
||||
import { Type } from "class-transformer"
|
||||
|
||||
import { OrderService } from "../../../../services"
|
||||
import { Type } from "class-transformer"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||
|
||||
import { defaultStoreOrdersFields, defaultStoreOrdersRelations } from "."
|
||||
import { FindParams } from "../../../../types/common"
|
||||
|
||||
/**
|
||||
* @oas [get] /orders
|
||||
@@ -18,6 +20,8 @@ import { validator } from "../../../../utils/validator"
|
||||
* description: "Look up an order using filters."
|
||||
* parameters:
|
||||
* - (query) display_id=* {number} The display id given to the Order.
|
||||
* - (query) fields {string} (Comma separated) Which fields should be included in the result.
|
||||
* - (query) expand {string} (Comma separated) Which fields should be expanded in the result.
|
||||
* - in: query
|
||||
* name: email
|
||||
* style: form
|
||||
@@ -79,7 +83,7 @@ import { validator } from "../../../../utils/validator"
|
||||
* $ref: "#/components/responses/500_error"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const validated = await validator(StoreGetOrdersParams, req.query)
|
||||
const validated = req.validatedQuery as StoreGetOrdersParams
|
||||
|
||||
const orderService: OrderService = req.scope.resolve("orderService")
|
||||
|
||||
@@ -101,7 +105,7 @@ export default async (req, res) => {
|
||||
|
||||
const order = orders[0]
|
||||
|
||||
res.json({ order })
|
||||
res.json({ order: cleanResponseData(order, req.allowedProperties || []) })
|
||||
}
|
||||
|
||||
export class ShippingAddressPayload {
|
||||
@@ -110,7 +114,7 @@ export class ShippingAddressPayload {
|
||||
postal_code?: string
|
||||
}
|
||||
|
||||
export class StoreGetOrdersParams {
|
||||
export class StoreGetOrdersParams extends FindParams {
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
display_id: number
|
||||
|
||||
@@ -20,7 +20,7 @@ export default (app, container) => {
|
||||
"/:id",
|
||||
transformQuery(GetPaymentCollectionsParams, {
|
||||
defaultFields: defaultPaymentCollectionFields,
|
||||
defaultRelations: defaulPaymentCollectionRelations,
|
||||
defaultRelations: defaultPaymentCollectionRelations,
|
||||
isList: false,
|
||||
}),
|
||||
middlewares.wrap(require("./get-payment-collection").default)
|
||||
@@ -69,7 +69,7 @@ export const defaultPaymentCollectionFields = [
|
||||
"metadata",
|
||||
]
|
||||
|
||||
export const defaulPaymentCollectionRelations = ["region", "payment_sessions"]
|
||||
export const defaultPaymentCollectionRelations = ["region", "payment_sessions"]
|
||||
|
||||
/**
|
||||
* @schema StorePaymentCollectionsRes
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { defaultStoreProductsRelations } from ".."
|
||||
import { defaultStoreProductsFields, defaultStoreProductsRelations } from ".."
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||
|
||||
@@ -21,9 +21,9 @@ describe("GET /store/products", () => {
|
||||
{ status: ["published"] },
|
||||
{
|
||||
relations: defaultStoreProductsRelations,
|
||||
select: defaultStoreProductsFields,
|
||||
skip: 0,
|
||||
take: 100,
|
||||
select: undefined,
|
||||
order: {
|
||||
created_at: "DESC",
|
||||
},
|
||||
@@ -52,12 +52,12 @@ describe("GET /store/products", () => {
|
||||
{ is_giftcard: true, status: ["published"] },
|
||||
{
|
||||
relations: defaultStoreProductsRelations,
|
||||
select: defaultStoreProductsFields,
|
||||
skip: 0,
|
||||
take: 100,
|
||||
order: {
|
||||
created_at: "DESC",
|
||||
},
|
||||
select: undefined,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "../../../../services"
|
||||
import { PriceSelectionParams } from "../../../../types/price-selection"
|
||||
import { FlagRouter } from "../../../../utils/flag-router"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||
|
||||
/**
|
||||
* @oas [get] /products/{id}
|
||||
@@ -22,6 +22,8 @@ import { validator } from "../../../../utils/validator"
|
||||
* - (query) sales_channel_id {string} The sales channel used when fetching the product.
|
||||
* - (query) cart_id {string} The ID of the customer's cart.
|
||||
* - (query) region_id {string} The ID of the region the customer is using. This is helpful to ensure correct prices are retrieved for a region.
|
||||
* - (query) fields {string} (Comma separated) Which fields should be included in the result.
|
||||
* - (query) expand {string} (Comma separated) Which fields should be expanded in each product of the result.
|
||||
* - in: query
|
||||
* name: currency_code
|
||||
* style: form
|
||||
@@ -72,7 +74,7 @@ import { validator } from "../../../../utils/validator"
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const validated = await validator(StoreGetProductsProductParams, req.query)
|
||||
const validated = req.validatedQuery as StoreGetProductsProductParams
|
||||
|
||||
const customer_id = req.user?.customer_id
|
||||
|
||||
@@ -123,11 +125,21 @@ export default async (req, res) => {
|
||||
sales_channel_id
|
||||
)
|
||||
|
||||
res.json({ product })
|
||||
res.json({
|
||||
product: cleanResponseData(product, req.allowedProperties || []),
|
||||
})
|
||||
}
|
||||
|
||||
export class StoreGetProductsProductParams extends PriceSelectionParams {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
sales_channel_id?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
fields?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
expand?: string
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import PublishableAPIKeysFeatureFlag from "../../../../loaders/feature-flags/pub
|
||||
import { validateProductSalesChannelAssociation } from "../../../middlewares/publishable-api-key/validate-product-sales-channel-association"
|
||||
import { validateSalesChannelParam } from "../../../middlewares/publishable-api-key/validate-sales-channel-param"
|
||||
import { StoreGetProductsParams } from "./list-products"
|
||||
import { StoreGetProductsProductParams } from "./get-product"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -29,11 +30,25 @@ export default (app, featureFlagRouter: FlagRouter) => {
|
||||
"/",
|
||||
transformQuery(StoreGetProductsParams, {
|
||||
defaultRelations: defaultStoreProductsRelations,
|
||||
defaultFields: defaultStoreProductsFields,
|
||||
allowedFields: allowedStoreProductsFields,
|
||||
allowedRelations: allowedStoreProductsRelations,
|
||||
isList: true,
|
||||
}),
|
||||
middlewares.wrap(require("./list-products").default)
|
||||
)
|
||||
route.get("/:id", middlewares.wrap(require("./get-product").default))
|
||||
|
||||
route.get(
|
||||
"/:id",
|
||||
transformQuery(StoreGetProductsProductParams, {
|
||||
defaultRelations: defaultStoreProductsRelations,
|
||||
defaultFields: defaultStoreProductsFields,
|
||||
allowedFields: allowedStoreProductsFields,
|
||||
allowedRelations: allowedStoreProductsRelations,
|
||||
}),
|
||||
middlewares.wrap(require("./get-product").default)
|
||||
)
|
||||
|
||||
route.post("/search", middlewares.wrap(require("./search").default))
|
||||
|
||||
return app
|
||||
@@ -51,6 +66,47 @@ export const defaultStoreProductsRelations = [
|
||||
"type",
|
||||
]
|
||||
|
||||
export const defaultStoreProductsFields: (keyof Product)[] = [
|
||||
"id",
|
||||
"title",
|
||||
"subtitle",
|
||||
"status",
|
||||
"external_id",
|
||||
"description",
|
||||
"handle",
|
||||
"is_giftcard",
|
||||
"discountable",
|
||||
"thumbnail",
|
||||
"profile_id",
|
||||
"collection_id",
|
||||
"type_id",
|
||||
"weight",
|
||||
"length",
|
||||
"height",
|
||||
"width",
|
||||
"hs_code",
|
||||
"origin_country",
|
||||
"mid_code",
|
||||
"material",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata",
|
||||
]
|
||||
|
||||
export const allowedStoreProductsFields = [
|
||||
...defaultStoreProductsFields,
|
||||
// TODO: order prop validation
|
||||
"variants.title",
|
||||
"variants.prices.amount",
|
||||
]
|
||||
|
||||
export const allowedStoreProductsRelations = [
|
||||
...defaultStoreProductsRelations,
|
||||
"variants.title",
|
||||
"variants.prices.amount",
|
||||
]
|
||||
|
||||
export * from "./list-products"
|
||||
export * from "./search"
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean"
|
||||
import { IsType } from "../../../../utils/validators/is-type"
|
||||
import { FlagRouter } from "../../../../utils/flag-router"
|
||||
import PublishableAPIKeysFeatureFlag from "../../../../loaders/feature-flags/publishable-api-keys"
|
||||
import { cleanResponseData } from "../../../../utils/clean-response-data"
|
||||
|
||||
/**
|
||||
* @oas [get] /products
|
||||
@@ -137,8 +138,8 @@ import PublishableAPIKeysFeatureFlag from "../../../../loaders/feature-flags/pub
|
||||
* - (query) include_category_children {boolean} Include category children when filtering by category_id.
|
||||
* - (query) offset=0 {integer} How many products to skip in the result.
|
||||
* - (query) limit=100 {integer} Limit the number of products returned.
|
||||
* - (query) expand {string} (Comma separated) Which fields should be expanded in each order of the result.
|
||||
* - (query) fields {string} (Comma separated) Which fields should be included in each order of the result.
|
||||
* - (query) expand {string} (Comma separated) Which fields should be expanded in each product of the result.
|
||||
* - (query) fields {string} (Comma separated) Which fields should be included in each product of the result.
|
||||
* - (query) order {string} the field used to order the products.
|
||||
* - (query) cart_id {string} The id of the Cart to set prices based on.
|
||||
* - (query) region_id {string} The id of the Region to set prices based on.
|
||||
@@ -241,7 +242,7 @@ export default async (req, res) => {
|
||||
)
|
||||
|
||||
res.json({
|
||||
products,
|
||||
products: cleanResponseData(products, req.allowedProperties || []),
|
||||
count,
|
||||
offset: validated.offset,
|
||||
limit: validated.limit,
|
||||
|
||||
@@ -16,6 +16,7 @@ declare global {
|
||||
listConfig: FindConfig<unknown>
|
||||
retrieveConfig: FindConfig<unknown>
|
||||
filterableFields: Record<string, unknown>
|
||||
allowedProperties: string[]
|
||||
errors: string[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { pick } from "lodash"
|
||||
|
||||
/**
|
||||
* Filter response data to contain props specified in the fields array.
|
||||
*
|
||||
* @param data - record or an array of records in the response
|
||||
* @param fields - record props allowed in the response
|
||||
*/
|
||||
function cleanResponseData<T>(data: T, fields: string[]) {
|
||||
if (!fields.length) {
|
||||
return data
|
||||
}
|
||||
|
||||
if (Array.isArray(data)) {
|
||||
return data.map((record) => pick(record, fields))
|
||||
}
|
||||
|
||||
return pick(data, fields)
|
||||
}
|
||||
|
||||
export { cleanResponseData }
|
||||
Reference in New Issue
Block a user