From 3a77e8a88fd327aca579bcd09d767d9315a04d7f Mon Sep 17 00:00:00 2001 From: Rares Stefan Date: Wed, 26 Apr 2023 17:23:29 +0200 Subject: [PATCH] feat(medusa): Middleware to add default SC on query if no SC already exist on it (#3694) --- .changeset/rotten-pans-rhyme.md | 5 ++ .../__tests__/admin/publishable-api-key.js | 10 +--- .../api/__tests__/store/products.js | 11 +++- .../store/products/ff-product-categories.ts | 60 +++++++++++-------- .../api/helpers/store-product-seeder.js | 8 ++- .../middlewares/with-default-sales-channel.ts | 45 ++++++++++++++ .../src/api/routes/store/products/index.ts | 3 + 7 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 .changeset/rotten-pans-rhyme.md create mode 100644 packages/medusa/src/api/middlewares/with-default-sales-channel.ts diff --git a/.changeset/rotten-pans-rhyme.md b/.changeset/rotten-pans-rhyme.md new file mode 100644 index 0000000000..95a4ff6313 --- /dev/null +++ b/.changeset/rotten-pans-rhyme.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +feat(medusa): Middleware to add default SC on store products query if no SC provided diff --git a/integration-tests/api/__tests__/admin/publishable-api-key.js b/integration-tests/api/__tests__/admin/publishable-api-key.js index edff187d3f..709c72388b 100644 --- a/integration-tests/api/__tests__/admin/publishable-api-key.js +++ b/integration-tests/api/__tests__/admin/publishable-api-key.js @@ -682,7 +682,7 @@ describe("Publishable API keys", () => { ) }) - it("returns all products if PK is not passed", async () => { + it("returns default product from default sales channel if PK is not passed", async () => { const api = useApi() await api.post( @@ -703,15 +703,9 @@ describe("Publishable API keys", () => { }, }) - expect(response.data.products.length).toBe(3) + expect(response.data.products.length).toBe(1) expect(response.data.products).toEqual( expect.arrayContaining([ - expect.objectContaining({ - id: product1.id, - }), - expect.objectContaining({ - id: product2.id, - }), expect.objectContaining({ id: product3.id, }), diff --git a/integration-tests/api/__tests__/store/products.js b/integration-tests/api/__tests__/store/products.js index acf82e462f..3d6583791a 100644 --- a/integration-tests/api/__tests__/store/products.js +++ b/integration-tests/api/__tests__/store/products.js @@ -5,7 +5,7 @@ const { initDb, useDb } = require("../../../helpers/use-db") const { simpleProductFactory, - simpleProductCategoryFactory, + simpleSalesChannelFactory, } = require("../../factories") const productSeeder = require("../../helpers/store-product-seeder") @@ -37,7 +37,14 @@ describe("/store/products", () => { describe("GET /store/products", () => { beforeEach(async () => { - await productSeeder(dbConnection) + const defaultSalesChannel = await simpleSalesChannelFactory( + dbConnection, + { + id: "sales-channel", + is_default: true, + } + ) + await productSeeder(dbConnection, defaultSalesChannel) await adminSeeder(dbConnection) }) diff --git a/integration-tests/api/__tests__/store/products/ff-product-categories.ts b/integration-tests/api/__tests__/store/products/ff-product-categories.ts index 8ec31a6a2c..af2d4dd060 100644 --- a/integration-tests/api/__tests__/store/products/ff-product-categories.ts +++ b/integration-tests/api/__tests__/store/products/ff-product-categories.ts @@ -1,11 +1,11 @@ +import { simpleSalesChannelFactory } from "../../../factories" + const path = require("path") const setupServer = require("../../../../helpers/setup-server") const { useApi } = require("../../../../helpers/use-api") const { initDb, useDb } = require("../../../../helpers/use-db") -const { - simpleProductCategoryFactory, -} = require("../../../factories") +const { simpleProductCategoryFactory } = require("../../../factories") const productSeeder = require("../../../helpers/store-product-seeder") const adminSeeder = require("../../../helpers/admin-seeder") @@ -26,7 +26,7 @@ describe("/store/products", () => { dbConnection = await initDb({ cwd }) medusaProcess = await setupServer({ cwd, - env: { MEDUSA_FF_PRODUCT_CATEGORIES: true } + env: { MEDUSA_FF_PRODUCT_CATEGORIES: true }, }) }) @@ -51,9 +51,15 @@ describe("/store/products", () => { const internalCategoryWithProductId = "inactive-category-with-product-id" beforeEach(async () => { - const manager = dbConnection.manager + const defaultSalesChannel = await simpleSalesChannelFactory( + dbConnection, + { + id: "sales-channel", + is_default: true, + } + ) - await productSeeder(dbConnection) + await productSeeder(dbConnection, defaultSalesChannel) await adminSeeder(dbConnection) categoryWithProduct = await simpleProductCategoryFactory(dbConnection, { @@ -76,25 +82,31 @@ describe("/store/products", () => { } ) - inactiveCategoryWithProduct = await simpleProductCategoryFactory(dbConnection, { - id: inactiveCategoryWithProductId, - name: "inactive category with Product", - products: [{ id: testProductFilteringId2 }], - parent_category: nestedCategoryWithProduct, - is_active: false, - is_internal: false, - rank: 0, - }) + inactiveCategoryWithProduct = await simpleProductCategoryFactory( + dbConnection, + { + id: inactiveCategoryWithProductId, + name: "inactive category with Product", + products: [{ id: testProductFilteringId2 }], + parent_category: nestedCategoryWithProduct, + is_active: false, + is_internal: false, + rank: 0, + } + ) - internalCategoryWithProduct = await simpleProductCategoryFactory(dbConnection, { - id: inactiveCategoryWithProductId, - name: "inactive category with Product", - products: [{ id: testProductFilteringId2 }], - parent_category: nestedCategoryWithProduct, - is_active: true, - is_internal: true, - rank: 1, - }) + internalCategoryWithProduct = await simpleProductCategoryFactory( + dbConnection, + { + id: inactiveCategoryWithProductId, + name: "inactive category with Product", + products: [{ id: testProductFilteringId2 }], + parent_category: nestedCategoryWithProduct, + is_active: true, + is_internal: true, + rank: 1, + } + ) nested2CategoryWithProduct = await simpleProductCategoryFactory( dbConnection, diff --git a/integration-tests/api/helpers/store-product-seeder.js b/integration-tests/api/helpers/store-product-seeder.js index adf3973c96..f2686e8ba5 100644 --- a/integration-tests/api/helpers/store-product-seeder.js +++ b/integration-tests/api/helpers/store-product-seeder.js @@ -13,7 +13,7 @@ const { ShippingProfileType, } = require("@medusajs/medusa") -module.exports = async (dataSource, data = {}) => { +module.exports = async (dataSource, defaultSalesChannel) => { const manager = dataSource.manager const yesterday = ((today) => new Date(today.setDate(today.getDate() - 1)))( @@ -138,6 +138,7 @@ module.exports = async (dataSource, data = {}) => { { id: "tag1", value: "123" }, { tag: "tag2", value: "456" }, ], + sales_channels: defaultSalesChannel ? [defaultSalesChannel] : [], }) p.images = [image] @@ -270,6 +271,7 @@ module.exports = async (dataSource, data = {}) => { { id: "tag1", value: "123" }, { tag: "tag2", value: "456" }, ], + sales_channels: defaultSalesChannel ? [defaultSalesChannel] : [], }) await manager.save(p1) @@ -326,6 +328,7 @@ module.exports = async (dataSource, data = {}) => { collection_id: "test-collection1", status: "published", tags: [{ id: "tag3", value: "123" }], + sales_channels: defaultSalesChannel ? [defaultSalesChannel] : [], }) await manager.save(product1) @@ -340,6 +343,7 @@ module.exports = async (dataSource, data = {}) => { collection_id: "test-collection2", status: "published", tags: [{ id: "tag4", value: "1234" }], + sales_channels: defaultSalesChannel ? [defaultSalesChannel] : [], }) await manager.save(product2) @@ -354,6 +358,7 @@ module.exports = async (dataSource, data = {}) => { collection_id: "test-collection1", status: "draft", tags: [{ id: "tag4", value: "1234" }], + sales_channels: defaultSalesChannel ? [defaultSalesChannel] : [], }) await manager.save(product3) @@ -367,6 +372,7 @@ module.exports = async (dataSource, data = {}) => { description: "test-product-description", type: { id: "test-type", value: "test-type" }, status: "published", + sales_channels: defaultSalesChannel ? [defaultSalesChannel] : [], }) await manager.save(gift_card) diff --git a/packages/medusa/src/api/middlewares/with-default-sales-channel.ts b/packages/medusa/src/api/middlewares/with-default-sales-channel.ts new file mode 100644 index 0000000000..07cba14688 --- /dev/null +++ b/packages/medusa/src/api/middlewares/with-default-sales-channel.ts @@ -0,0 +1,45 @@ +import { NextFunction, Request, Response } from "express" +import SalesChannelFeatureFlag from "../../loaders/feature-flags/sales-channels" +import { SalesChannelService } from "../../services" +import { FlagRouter } from "../../utils/flag-router" + +/** + * Middleware that includes the default sales channel on the request, if no sales channels present + * @param context Object of options + * @param context.attachChannelAsArray Whether to attach the default sales channel as an array or just a string + */ +export function withDefaultSalesChannel({ + attachChannelAsArray, +}: { + attachChannelAsArray?: boolean +}): (req: Request, res: Response, next: NextFunction) => Promise { + return async (req: Request, _, next: NextFunction) => { + const featureFlagRouter = req.scope.resolve( + "featureFlagRouter" + ) as FlagRouter + + if ( + !featureFlagRouter.isFeatureEnabled(SalesChannelFeatureFlag.key) || + req.query.sales_channel_id?.length || + req.get("x-publishable-api-key") + ) { + return next() + } + + const salesChannelService: SalesChannelService = req.scope.resolve( + "salesChannelService" + ) + + try { + const defaultSalesChannel = await salesChannelService.retrieveDefault() + if (defaultSalesChannel?.id) { + req.query.sales_channel_id = attachChannelAsArray + ? [defaultSalesChannel.id] + : defaultSalesChannel.id + } + } catch { + } finally { + next() + } + } +} diff --git a/packages/medusa/src/api/routes/store/products/index.ts b/packages/medusa/src/api/routes/store/products/index.ts index 5e448e59ef..db0ad37d3b 100644 --- a/packages/medusa/src/api/routes/store/products/index.ts +++ b/packages/medusa/src/api/routes/store/products/index.ts @@ -12,6 +12,7 @@ import { StoreGetProductsProductParams } from "./get-product" import { extendRequestParams } from "../../../middlewares/publishable-api-key/extend-request-params" import { validateProductSalesChannelAssociation } from "../../../middlewares/publishable-api-key/validate-product-sales-channel-association" import { validateSalesChannelParam } from "../../../middlewares/publishable-api-key/validate-sales-channel-param" +import { withDefaultSalesChannel } from "../../../middlewares/with-default-sales-channel" const route = Router() @@ -26,6 +27,7 @@ export default (app, featureFlagRouter: FlagRouter) => { route.get( "/", + withDefaultSalesChannel({ attachChannelAsArray: true }), transformStoreQuery(StoreGetProductsParams, { defaultRelations: defaultStoreProductsRelations, defaultFields: defaultStoreProductsFields, @@ -38,6 +40,7 @@ export default (app, featureFlagRouter: FlagRouter) => { route.get( "/:id", + withDefaultSalesChannel({}), transformStoreQuery(StoreGetProductsProductParams, { defaultRelations: defaultStoreProductsRelations, defaultFields: defaultStoreProductsFields,