Feat(medusa, medusa-js, medusa-react): Include sales channels in related queries as an optional expand parameter (#1816)
**What** - Add `transformQuery` to get endpoints for product, order and cart - ensure that the default relations (when getting a singular entity) includes sales channels when enabled - Add `EmptyQueryParams` class in common types to prevent query parameters while using `transformQuery` - update product-, order- and cartFactory to include sales channels if provided - remove `packages/medusa/src/controllers/products/admin-list-products.ts` **Testing** - expands sales channel for single order - expands sales channels for orders with expand parameter - returns single product with sales channel - expands sales channels for products with expand parameter - returns cart with sales channel for single cart Fixes CORE-293 Co-authored-by: Sebastian Rindom <7554214+srindom@users.noreply.github.com> Co-authored-by: Adrien de Peretti <25098370+adrien2p@users.noreply.github.com>
This commit is contained in:
co-authored by
Sebastian Rindom
Adrien de Peretti
parent
fb4cfc3c3c
commit
19f35ba6aa
@@ -44,6 +44,8 @@ export default (app, container, config) => {
|
||||
})
|
||||
)
|
||||
|
||||
const featureFlagRouter = container.resolve("featureFlagRouter")
|
||||
|
||||
// Unauthenticated routes
|
||||
authRoutes(route)
|
||||
|
||||
@@ -74,9 +76,9 @@ export default (app, container, config) => {
|
||||
inviteRoutes(route)
|
||||
noteRoutes(route)
|
||||
notificationRoutes(route)
|
||||
orderRoutes(route)
|
||||
orderRoutes(route, featureFlagRouter)
|
||||
priceListRoutes(route)
|
||||
productRoutes(route)
|
||||
productRoutes(route, featureFlagRouter)
|
||||
productTagRoutes(route)
|
||||
productTypesRoutes(route)
|
||||
regionRoutes(route)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { defaultAdminOrdersRelations, defaultAdminOrdersFields } from "."
|
||||
import { OrderService } from "../../../../services"
|
||||
|
||||
/**
|
||||
@@ -26,10 +25,7 @@ export default async (req, res) => {
|
||||
|
||||
const orderService: OrderService = req.scope.resolve("orderService")
|
||||
|
||||
const order = await orderService.retrieve(id, {
|
||||
select: defaultAdminOrdersFields,
|
||||
relations: defaultAdminOrdersRelations,
|
||||
})
|
||||
const order = await orderService.retrieve(id, req.retrieveConfig)
|
||||
|
||||
res.json({ order })
|
||||
}
|
||||
|
||||
@@ -1,20 +1,36 @@
|
||||
import { Router } from "express"
|
||||
import "reflect-metadata"
|
||||
import { Order } from "../../../.."
|
||||
import { DeleteResponse, PaginatedResponse } from "../../../../types/common"
|
||||
import middlewares from "../../../middlewares"
|
||||
import {
|
||||
DeleteResponse,
|
||||
EmptyQueryParams,
|
||||
PaginatedResponse,
|
||||
} from "../../../../types/common"
|
||||
import middlewares, { transformQuery } from "../../../middlewares"
|
||||
import { AdminGetOrdersParams } from "./list-orders"
|
||||
import { FlagRouter } from "../../../../utils/flag-router"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default (app) => {
|
||||
export default (app, featureFlagRouter: FlagRouter) => {
|
||||
app.use("/orders", route)
|
||||
|
||||
const relations = [...defaultAdminOrdersRelations]
|
||||
if (featureFlagRouter.isFeatureEnabled("sales_channels")) {
|
||||
relations.push("sales_channel")
|
||||
}
|
||||
|
||||
/**
|
||||
* List orders
|
||||
*/
|
||||
route.get(
|
||||
"/",
|
||||
middlewares.normalizeQuery(),
|
||||
transformQuery(AdminGetOrdersParams, {
|
||||
defaultRelations: relations,
|
||||
defaultFields: defaultAdminOrdersFields,
|
||||
allowedFields: allowedAdminOrdersFields,
|
||||
isList: true,
|
||||
}),
|
||||
middlewares.wrap(require("./list-orders").default)
|
||||
)
|
||||
|
||||
@@ -23,7 +39,12 @@ export default (app) => {
|
||||
*/
|
||||
route.get(
|
||||
"/:id",
|
||||
middlewares.normalizeQuery(),
|
||||
transformQuery(EmptyQueryParams, {
|
||||
defaultRelations: relations,
|
||||
defaultFields: defaultAdminOrdersFields,
|
||||
allowedFields: allowedAdminOrdersFields,
|
||||
isList: false,
|
||||
}),
|
||||
middlewares.wrap(require("./get-order").default)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { defaultAdminOrdersRelations, defaultAdminOrdersFields } from "."
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { IsNumber, IsOptional, IsString } from "class-validator"
|
||||
import { omit, pick, pickBy } from "lodash"
|
||||
import { pick } from "lodash"
|
||||
import { OrderService } from "../../../../services"
|
||||
import { AdminListOrdersSelector } from "../../../../types/orders"
|
||||
import { Type } from "class-transformer"
|
||||
@@ -47,51 +45,23 @@ import { Type } from "class-transformer"
|
||||
* $ref: "#/components/schemas/order"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const value = await validator(AdminGetOrdersParams, req.query)
|
||||
|
||||
const orderService: OrderService = req.scope.resolve("orderService")
|
||||
|
||||
let includeFields: string[] = []
|
||||
if (value.fields) {
|
||||
includeFields = value.fields.split(",")
|
||||
// Ensure created_at is included, since we are sorting on this
|
||||
includeFields.push("created_at")
|
||||
}
|
||||
|
||||
let expandFields: string[] = []
|
||||
if (value.expand) {
|
||||
expandFields = value.expand.split(",")
|
||||
}
|
||||
|
||||
const listConfig = {
|
||||
select: includeFields.length ? includeFields : defaultAdminOrdersFields,
|
||||
relations: expandFields.length ? expandFields : defaultAdminOrdersRelations,
|
||||
skip: value.offset,
|
||||
take: value.limit,
|
||||
order: { created_at: "DESC" },
|
||||
}
|
||||
|
||||
const filterableFields = omit(value, [
|
||||
"limit",
|
||||
"offset",
|
||||
"expand",
|
||||
"fields",
|
||||
"order",
|
||||
])
|
||||
const { skip, take, select, relations } = req.listConfig
|
||||
|
||||
const [orders, count] = await orderService.listAndCount(
|
||||
pickBy(filterableFields, (val) => typeof val !== "undefined"),
|
||||
listConfig
|
||||
req.filterableFields,
|
||||
req.listConfig
|
||||
)
|
||||
|
||||
let data = orders
|
||||
|
||||
const fields = [...includeFields, ...expandFields]
|
||||
const fields = [...select, ...relations]
|
||||
if (fields.length) {
|
||||
data = orders.map((o) => pick(o, fields))
|
||||
}
|
||||
|
||||
res.json({ orders: data, count, offset: value.offset, limit: value.limit })
|
||||
res.json({ orders: data, count, offset: skip, limit: take })
|
||||
}
|
||||
|
||||
export class AdminGetOrdersParams extends AdminListOrdersSelector {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { defaultAdminProductFields, defaultAdminProductRelations } from "."
|
||||
import { ProductService, PricingService } from "../../../../services"
|
||||
|
||||
/**
|
||||
@@ -27,10 +26,7 @@ export default async (req, res) => {
|
||||
const productService: ProductService = req.scope.resolve("productService")
|
||||
const pricingService: PricingService = req.scope.resolve("pricingService")
|
||||
|
||||
const rawProduct = await productService.retrieve(id, {
|
||||
select: defaultAdminProductFields,
|
||||
relations: defaultAdminProductRelations,
|
||||
})
|
||||
const rawProduct = await productService.retrieve(id, req.retrieveConfig)
|
||||
|
||||
const [product] = await pricingService.setProductPrices([rawProduct])
|
||||
|
||||
|
||||
@@ -2,14 +2,21 @@ import { Router } from "express"
|
||||
import "reflect-metadata"
|
||||
import { PricedProduct } from "../../../../types/pricing"
|
||||
import { Product, ProductTag, ProductType } from "../../../.."
|
||||
import { PaginatedResponse } from "../../../../types/common"
|
||||
import middlewares from "../../../middlewares"
|
||||
import { EmptyQueryParams, PaginatedResponse } from "../../../../types/common"
|
||||
import middlewares, { transformQuery } from "../../../middlewares"
|
||||
import { AdminGetProductsParams } from "./list-products"
|
||||
import { FlagRouter } from "../../../../utils/flag-router"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default (app) => {
|
||||
export default (app, featureFlagRouter: FlagRouter) => {
|
||||
app.use("/products", route)
|
||||
|
||||
const relations = [...defaultAdminProductRelations]
|
||||
if (featureFlagRouter.isFeatureEnabled("sales_channels")) {
|
||||
relations.push("sales_channels")
|
||||
}
|
||||
|
||||
route.post("/", middlewares.wrap(require("./create-product").default))
|
||||
route.post("/:id", middlewares.wrap(require("./update-product").default))
|
||||
route.get("/types", middlewares.wrap(require("./list-types").default))
|
||||
@@ -53,11 +60,25 @@ export default (app) => {
|
||||
"/:id/metadata",
|
||||
middlewares.wrap(require("./set-metadata").default)
|
||||
)
|
||||
route.get(
|
||||
"/:id",
|
||||
transformQuery(EmptyQueryParams, {
|
||||
defaultRelations: relations,
|
||||
defaultFields: defaultAdminProductFields,
|
||||
allowedFields: allowedAdminProductFields,
|
||||
isList: false,
|
||||
}),
|
||||
middlewares.wrap(require("./get-product").default)
|
||||
)
|
||||
|
||||
route.get("/:id", middlewares.wrap(require("./get-product").default))
|
||||
route.get(
|
||||
"/",
|
||||
middlewares.normalizeQuery(),
|
||||
transformQuery(AdminGetProductsParams, {
|
||||
defaultRelations: defaultAdminProductRelations,
|
||||
defaultFields: defaultAdminProductFields,
|
||||
allowedFields: allowedAdminProductFields,
|
||||
isList: true,
|
||||
}),
|
||||
middlewares.wrap(require("./list-products").default)
|
||||
)
|
||||
|
||||
@@ -141,6 +162,7 @@ export const allowedAdminProductRelations = [
|
||||
"tags",
|
||||
"type",
|
||||
"collection",
|
||||
"sales_channels",
|
||||
]
|
||||
|
||||
export type AdminProductsDeleteOptionRes = {
|
||||
|
||||
@@ -8,17 +8,11 @@ import {
|
||||
IsString,
|
||||
ValidateNested,
|
||||
} from "class-validator"
|
||||
import { omit } from "lodash"
|
||||
import { Product, ProductStatus } from "../../../../models/product"
|
||||
import { DateComparisonOperator } from "../../../../types/common"
|
||||
import {
|
||||
allowedAdminProductFields,
|
||||
defaultAdminProductFields,
|
||||
defaultAdminProductRelations,
|
||||
} from "."
|
||||
import listAndCount from "../../../../controllers/products/admin-list-products"
|
||||
import { validator } from "../../../../utils/validator"
|
||||
import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean"
|
||||
import { PricedProduct } from "../../../../types/pricing"
|
||||
import { PricingService, ProductService } from "../../../../services"
|
||||
|
||||
/**
|
||||
* @oas [get] /products
|
||||
@@ -69,32 +63,31 @@ import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean"
|
||||
* $ref: "#/components/schemas/product"
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const validatedParams = await validator(AdminGetProductsParams, req.query)
|
||||
const productService: ProductService = req.scope.resolve("productService")
|
||||
const pricingService: PricingService = req.scope.resolve("pricingService")
|
||||
|
||||
const filterableFields = omit(validatedParams, [
|
||||
"limit",
|
||||
"offset",
|
||||
"expand",
|
||||
"fields",
|
||||
"order",
|
||||
])
|
||||
const { skip, take, relations } = req.listConfig
|
||||
|
||||
const result = await listAndCount(
|
||||
req.scope,
|
||||
filterableFields,
|
||||
{},
|
||||
{
|
||||
limit: validatedParams.limit ?? 50,
|
||||
offset: validatedParams.offset ?? 0,
|
||||
expand: validatedParams.expand,
|
||||
fields: validatedParams.fields,
|
||||
allowedFields: allowedAdminProductFields,
|
||||
defaultFields: defaultAdminProductFields as (keyof Product)[],
|
||||
defaultRelations: defaultAdminProductRelations,
|
||||
}
|
||||
const [rawProducts, count] = await productService.listAndCount(
|
||||
req.filterableFields,
|
||||
req.listConfig
|
||||
)
|
||||
|
||||
res.json(result)
|
||||
let products: (Product | PricedProduct)[] = rawProducts
|
||||
|
||||
const includesPricing = ["variants", "variants.prices"].every((relation) =>
|
||||
relations?.includes(relation)
|
||||
)
|
||||
if (includesPricing) {
|
||||
products = await pricingService.setProductPrices(rawProducts)
|
||||
}
|
||||
|
||||
res.json({
|
||||
products,
|
||||
count,
|
||||
offset: skip,
|
||||
limit: take,
|
||||
})
|
||||
}
|
||||
|
||||
export class AdminGetProductsPaginationParams {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { defaultStoreCartFields, defaultStoreCartRelations } from "."
|
||||
import { CartService } from "../../../../services"
|
||||
import { decorateLineItemsWithTotals } from "./decorate-line-items-with-totals"
|
||||
|
||||
@@ -43,10 +42,7 @@ export default async (req, res) => {
|
||||
}
|
||||
}
|
||||
|
||||
cart = await cartService.retrieve(id, {
|
||||
select: defaultStoreCartFields,
|
||||
relations: defaultStoreCartRelations,
|
||||
})
|
||||
cart = await cartService.retrieve(id, req.retrieveConfig)
|
||||
|
||||
const data = await decorateLineItemsWithTotals(cart, req)
|
||||
res.json({ cart: data })
|
||||
|
||||
@@ -1,22 +1,36 @@
|
||||
import { Router } from "express"
|
||||
import "reflect-metadata"
|
||||
import { Cart, Order, Swap } from "../../../../"
|
||||
import { DeleteResponse } from "../../../../types/common"
|
||||
import middlewares from "../../../middlewares"
|
||||
import { DeleteResponse, EmptyQueryParams } from "../../../../types/common"
|
||||
import middlewares, { transformQuery } from "../../../middlewares"
|
||||
const route = Router()
|
||||
|
||||
export default (app, container) => {
|
||||
const middlewareService = container.resolve("middlewareService")
|
||||
const featureFlagRouter = container.resolve("featureFlagRouter")
|
||||
|
||||
app.use("/carts", route)
|
||||
|
||||
const relations = [...defaultStoreCartRelations]
|
||||
if (featureFlagRouter.isFeatureEnabled("sales_channels")) {
|
||||
relations.push("sales_channel")
|
||||
}
|
||||
|
||||
// Inject plugin routes
|
||||
const routers = middlewareService.getRouters("store/carts")
|
||||
for (const router of routers) {
|
||||
route.use("/", router)
|
||||
}
|
||||
|
||||
route.get("/:id", middlewares.wrap(require("./get-cart").default))
|
||||
route.get(
|
||||
"/:id",
|
||||
transformQuery(EmptyQueryParams, {
|
||||
defaultRelations: relations,
|
||||
defaultFields: defaultStoreCartFields,
|
||||
isList: false,
|
||||
}),
|
||||
middlewares.wrap(require("./get-cart").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/",
|
||||
|
||||
Reference in New Issue
Block a user