From 0f4cb50326931f79030d3ff13792e7a22fdd765d Mon Sep 17 00:00:00 2001 From: olivermrbl Date: Thu, 29 Sep 2022 11:58:18 +0200 Subject: [PATCH 1/2] fix(medusa): Query products in sales channel in storefront --- .../api/__tests__/store/sales-channels.js | 97 ++++++++++++++++++- .../api/factories/simple-product-factory.ts | 8 +- .../routes/admin/products/list-products.ts | 6 +- .../routes/store/products/list-products.ts | 34 ++++--- packages/medusa/src/types/product.ts | 12 ++- 5 files changed, 131 insertions(+), 26 deletions(-) diff --git a/integration-tests/api/__tests__/store/sales-channels.js b/integration-tests/api/__tests__/store/sales-channels.js index ef4009a35af2d..69078e01e8d91 100644 --- a/integration-tests/api/__tests__/store/sales-channels.js +++ b/integration-tests/api/__tests__/store/sales-channels.js @@ -126,8 +126,11 @@ describe("sales channels", () => { }) describe("POST /store/cart/:id", () => { - let salesChannel1, salesChannel2, disabledSalesChannel - let product1, product2 + let salesChannel1 + let salesChannel2 + let disabledSalesChannel + let product1 + let product2 let cart beforeEach(async () => { @@ -289,4 +292,94 @@ describe("sales channels", () => { }) }) }) + + describe("GET /store/products", () => { + let salesChannel1 + let salesChannel2 + let product1 + let product2 + beforeEach(async () => { + salesChannel1 = await simpleSalesChannelFactory(dbConnection, { + name: "salesChannel1", + description: "salesChannel1", + }) + + salesChannel2 = await simpleSalesChannelFactory(dbConnection, { + name: "salesChannel2", + description: "salesChannel2", + }) + + product1 = await simpleProductFactory(dbConnection, { + title: "prod 1", + status: "published", + sales_channels: [salesChannel1], + }) + + product2 = await simpleProductFactory(dbConnection, { + title: "prod 2", + status: "published", + sales_channels: [salesChannel2], + }) + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + }) + + it("returns products from a specific sales channel", async () => { + const api = useApi() + + const response = await api.get( + `/store/products?sales_channel_id[]=${salesChannel1.id}` + ) + + expect(response.data.products.length).toBe(1) + expect(response.data.products).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + }), + ]) + ) + }) + + it("returns products from multiples sales channels", async () => { + const api = useApi() + + const response = await api.get( + `/store/products?sales_channel_id[]=${salesChannel1.id}&sales_channel_id[]=${salesChannel2.id}` + ) + + expect(response.data.products.length).toBe(2) + expect(response.data.products).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + }), + expect.objectContaining({ + id: expect.any(String), + }), + ]) + ) + }) + + it("returns all products by default", async () => { + const api = useApi() + + const response = await api.get(`/store/products`) + + expect(response.data.products.length).toBe(2) + expect(response.data.products).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + }), + expect.objectContaining({ + id: expect.any(String), + }), + ]) + ) + }) + }) }) diff --git a/integration-tests/api/factories/simple-product-factory.ts b/integration-tests/api/factories/simple-product-factory.ts index 304043028df1e..0c6f038fa10e0 100644 --- a/integration-tests/api/factories/simple-product-factory.ts +++ b/integration-tests/api/factories/simple-product-factory.ts @@ -4,17 +4,17 @@ import { ProductTag, ProductType, ShippingProfile, - ShippingProfileType, + ShippingProfileType } from "@medusajs/medusa" import faker from "faker" import { Connection } from "typeorm" import { ProductVariantFactoryData, - simpleProductVariantFactory, + simpleProductVariantFactory } from "./simple-product-variant-factory" import { SalesChannelFactoryData, - simpleSalesChannelFactory, + simpleSalesChannelFactory } from "./simple-sales-channel-factory" export type ProductFactoryData = { @@ -94,7 +94,7 @@ export const simpleProductFactory = async ( toSave.sales_channels = sales_channels - await manager.save(toSave) + const test = await manager.save(toSave) const optionId = `${prodId}-option` const options = data.options || [{ id: optionId, title: "Size" }] diff --git a/packages/medusa/src/api/routes/admin/products/list-products.ts b/packages/medusa/src/api/routes/admin/products/list-products.ts index e2e1e72429efa..5e0fa2fab367f 100644 --- a/packages/medusa/src/api/routes/admin/products/list-products.ts +++ b/packages/medusa/src/api/routes/admin/products/list-products.ts @@ -1,10 +1,10 @@ import { IsNumber, IsOptional, IsString } from "class-validator" import { PricingService, ProductService } from "../../../../services" -import { FilterableProductProps } from "../../../../types/product" -import { PricedProduct } from "../../../../types/pricing" -import { Product } from "../../../../models" import { Type } from "class-transformer" +import { Product } from "../../../../models" +import { PricedProduct } from "../../../../types/pricing" +import { FilterableProductProps } from "../../../../types/product" /** * @oas [get] /products diff --git a/packages/medusa/src/api/routes/store/products/list-products.ts b/packages/medusa/src/api/routes/store/products/list-products.ts index e6bd6d5b1686a..114e744f4ab9e 100644 --- a/packages/medusa/src/api/routes/store/products/list-products.ts +++ b/packages/medusa/src/api/routes/store/products/list-products.ts @@ -1,28 +1,30 @@ -import { - CartService, - ProductService, - RegionService, -} from "../../../../services" +import { Transform, Type } from "class-transformer" import { IsArray, IsBoolean, IsNumber, IsOptional, IsString, - ValidateNested, + ValidateNested } from "class-validator" -import { Transform, Type } from "class-transformer" import { omit, pickBy } from "lodash" +import { + CartService, + ProductService, + RegionService +} from "../../../../services" +import { defaultStoreProductsRelations } from "." +import SalesChannelFeatureFlag from "../../../../loaders/feature-flags/sales-channels" +import { Product } from "../../../../models" +import PricingService from "../../../../services/pricing" import { DateComparisonOperator } from "../../../../types/common" -import { IsType } from "../../../../utils/validators/is-type" import { PriceSelectionParams } from "../../../../types/price-selection" -import PricingService from "../../../../services/pricing" -import { Product } from "../../../../models" -import { defaultStoreProductsRelations } from "." -import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean" -import { validator } from "../../../../utils/validator" import { isDefined } from "../../../../utils" +import { FeatureFlagDecorators } from "../../../../utils/feature-flag-decorators" +import { validator } from "../../../../utils/validator" +import { optionalBooleanMapper } from "../../../../utils/validators/is-boolean" +import { IsType } from "../../../../utils/validators/is-type" /** * @oas [get] /products @@ -294,6 +296,12 @@ export class StoreGetProductsParams extends StoreGetProductsPaginationParams { @IsOptional() type?: string + @FeatureFlagDecorators(SalesChannelFeatureFlag.key, [ + IsOptional(), + IsArray(), + ]) + sales_channel_id?: string[] + @IsOptional() @ValidateNested() @Type(() => DateComparisonOperator) diff --git a/packages/medusa/src/types/product.ts b/packages/medusa/src/types/product.ts index 8e6d6036c5b61..dcfd6a22df0bf 100644 --- a/packages/medusa/src/types/product.ts +++ b/packages/medusa/src/types/product.ts @@ -5,15 +5,17 @@ import { IsEnum, IsOptional, IsString, - ValidateNested, + ValidateNested } from "class-validator" +import SalesChannelFeatureFlag from "../loaders/feature-flags/sales-channels" import { Product, ProductOptionValue, ProductStatus } from "../models" +import { FeatureFlagDecorators } from "../utils/feature-flag-decorators" import { optionalBooleanMapper } from "../utils/validators/is-boolean" import { IsType } from "../utils/validators/is-type" import { DateComparisonOperator, FindConfig, - StringComparisonOperator, + StringComparisonOperator } from "./common" import { PriceListLoadConfig } from "./price-list" @@ -66,8 +68,10 @@ export class FilterableProductProps { @IsOptional() type?: string - @IsArray() - @IsOptional() + @FeatureFlagDecorators(SalesChannelFeatureFlag.key, [ + IsOptional(), + IsArray(), + ]) sales_channel_id?: string[] @IsOptional() From e531d29325db56b727b6a71153c95aa0dfc9d06a Mon Sep 17 00:00:00 2001 From: olivermrbl Date: Thu, 29 Sep 2022 12:01:02 +0200 Subject: [PATCH 2/2] remove log --- integration-tests/api/factories/simple-product-factory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/api/factories/simple-product-factory.ts b/integration-tests/api/factories/simple-product-factory.ts index 0c6f038fa10e0..d0b3ad4b4340b 100644 --- a/integration-tests/api/factories/simple-product-factory.ts +++ b/integration-tests/api/factories/simple-product-factory.ts @@ -94,7 +94,7 @@ export const simpleProductFactory = async ( toSave.sales_channels = sales_channels - const test = await manager.save(toSave) + await manager.save(toSave) const optionId = `${prodId}-option` const options = data.options || [{ id: optionId, title: "Size" }]