From 4ce12b9bd92b7ff9ebadf9e272d7a9ae1db63332 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 13 May 2024 16:42:38 +0530 Subject: [PATCH 01/12] fix: allow URL safe characters for product handle Fixes: CORE-2072 --- packages/medusa/src/api-v2/admin/products/validators.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/medusa/src/api-v2/admin/products/validators.ts b/packages/medusa/src/api-v2/admin/products/validators.ts index 5a2b2b006cdd9..b71b90b2f62aa 100644 --- a/packages/medusa/src/api-v2/admin/products/validators.ts +++ b/packages/medusa/src/api-v2/admin/products/validators.ts @@ -177,7 +177,10 @@ export const AdminCreateProduct = z discountable: z.boolean().optional().default(true), images: z.array(z.object({ url: z.string() })).optional(), thumbnail: z.string().optional(), - handle: z.string().optional(), + handle: z + .string() + .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/) + .optional(), status: statusEnum.optional().default(ProductStatus.DRAFT), type_id: z.string().nullable().optional(), collection_id: z.string().nullable().optional(), From 6438d1f6daa00bfe8c8e9edf227789d51e8b7c1a Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 13 May 2024 16:44:04 +0530 Subject: [PATCH 02/12] fix: allow URL safe only values for collection handle --- .../medusa/src/api-v2/admin/collections/validators.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/medusa/src/api-v2/admin/collections/validators.ts b/packages/medusa/src/api-v2/admin/collections/validators.ts index 687d6e8a1c04a..77bd03861ff7d 100644 --- a/packages/medusa/src/api-v2/admin/collections/validators.ts +++ b/packages/medusa/src/api-v2/admin/collections/validators.ts @@ -29,13 +29,19 @@ export const AdminGetCollectionsParams = createFindParams({ export type AdminCreateCollectionType = z.infer export const AdminCreateCollection = z.object({ title: z.string(), - handle: z.string().optional(), + handle: z + .string() + .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/) + .optional(), metadata: z.record(z.unknown()).optional(), }) export type AdminUpdateCollectionType = z.infer export const AdminUpdateCollection = z.object({ title: z.string().optional(), - handle: z.string().optional(), + handle: z + .string() + .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/) + .optional(), metadata: z.record(z.unknown()).optional(), }) From 4e7ec0fd6a3be32f4c41a9b1e2158cf915cd4fff Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 13 May 2024 17:16:52 +0530 Subject: [PATCH 03/12] refactor: abstract entity handle validator to common set of validators --- .../medusa/src/api-v2/admin/collections/validators.ts | 11 +++-------- .../medusa/src/api-v2/admin/products/validators.ts | 10 +++++----- .../src/api-v2/utils/common-validators/common.ts | 8 ++++++++ 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/medusa/src/api-v2/admin/collections/validators.ts b/packages/medusa/src/api-v2/admin/collections/validators.ts index 77bd03861ff7d..6088451f0fb17 100644 --- a/packages/medusa/src/api-v2/admin/collections/validators.ts +++ b/packages/medusa/src/api-v2/admin/collections/validators.ts @@ -1,3 +1,4 @@ +import { HandleValidator } from "../../utils/common-validators" import { createFindParams, createOperatorMap, @@ -29,19 +30,13 @@ export const AdminGetCollectionsParams = createFindParams({ export type AdminCreateCollectionType = z.infer export const AdminCreateCollection = z.object({ title: z.string(), - handle: z - .string() - .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/) - .optional(), + handle: HandleValidator, metadata: z.record(z.unknown()).optional(), }) export type AdminUpdateCollectionType = z.infer export const AdminUpdateCollection = z.object({ title: z.string().optional(), - handle: z - .string() - .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/) - .optional(), + handle: HandleValidator, metadata: z.record(z.unknown()).optional(), }) diff --git a/packages/medusa/src/api-v2/admin/products/validators.ts b/packages/medusa/src/api-v2/admin/products/validators.ts index b71b90b2f62aa..93884b0cd547c 100644 --- a/packages/medusa/src/api-v2/admin/products/validators.ts +++ b/packages/medusa/src/api-v2/admin/products/validators.ts @@ -1,6 +1,9 @@ import { ProductStatus } from "@medusajs/utils" import { z } from "zod" -import { GetProductsParams } from "../../utils/common-validators" +import { + GetProductsParams, + HandleValidator, +} from "../../utils/common-validators" import { createFindParams, createOperatorMap, @@ -177,10 +180,7 @@ export const AdminCreateProduct = z discountable: z.boolean().optional().default(true), images: z.array(z.object({ url: z.string() })).optional(), thumbnail: z.string().optional(), - handle: z - .string() - .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/) - .optional(), + handle: HandleValidator, status: statusEnum.optional().default(ProductStatus.DRAFT), type_id: z.string().nullable().optional(), collection_id: z.string().nullable().optional(), diff --git a/packages/medusa/src/api-v2/utils/common-validators/common.ts b/packages/medusa/src/api-v2/utils/common-validators/common.ts index f8506390e905b..14f984158e777 100644 --- a/packages/medusa/src/api-v2/utils/common-validators/common.ts +++ b/packages/medusa/src/api-v2/utils/common-validators/common.ts @@ -36,3 +36,11 @@ export const OptionalBooleanValidator = z.preprocess( (val: any) => optionalBooleanMapper.get(val?.toLowerCase()), z.boolean().optional() ) + +/** + * Validates entity handle to have URL-safe characters + */ +export const HandleValidator = z + .string() + .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/) + .optional() From 3b529356eba2f13aa9ba2ef0934b01daf7228ce1 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 13 May 2024 20:35:42 +0530 Subject: [PATCH 04/12] feat: add utils for normalizing and validation handles --- .../common/__tests__/is-valid-handle.spec.ts | 36 +++++++++++++++ .../common/__tests__/normalize-handle.spec.ts | 44 +++++++++++++++++++ packages/core/utils/src/common/index.ts | 2 + .../core/utils/src/common/normalize-handle.ts | 18 ++++++++ .../core/utils/src/common/to-kebab-case.ts | 2 +- .../core/utils/src/common/validate-handle.ts | 9 ++++ 6 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 packages/core/utils/src/common/__tests__/is-valid-handle.spec.ts create mode 100644 packages/core/utils/src/common/__tests__/normalize-handle.spec.ts create mode 100644 packages/core/utils/src/common/normalize-handle.ts create mode 100644 packages/core/utils/src/common/validate-handle.ts diff --git a/packages/core/utils/src/common/__tests__/is-valid-handle.spec.ts b/packages/core/utils/src/common/__tests__/is-valid-handle.spec.ts new file mode 100644 index 0000000000000..4d89e04d115d7 --- /dev/null +++ b/packages/core/utils/src/common/__tests__/is-valid-handle.spec.ts @@ -0,0 +1,36 @@ +import { isValidHandle } from "../validate-handle" + +describe("normalizeHandle", function () { + it("should generate URL friendly handles", function () { + const expectations = [ + { + input: "the-fan-boy's-club", + isValid: false, + }, + { + input: "@t-the-sky", + isValid: false, + }, + { + input: "nouvelles-annees", + isValid: true, + }, + { + input: "@t-the-sky", + isValid: false, + }, + { + input: "user.product", + isValid: false, + }, + { + input: 'sky"bar', + isValid: false, + }, + ] + + expectations.forEach((expectation) => { + expect(isValidHandle(expectation.input)).toEqual(expectation.isValid) + }) + }) +}) diff --git a/packages/core/utils/src/common/__tests__/normalize-handle.spec.ts b/packages/core/utils/src/common/__tests__/normalize-handle.spec.ts new file mode 100644 index 0000000000000..b5293ccd67b1c --- /dev/null +++ b/packages/core/utils/src/common/__tests__/normalize-handle.spec.ts @@ -0,0 +1,44 @@ +import { normalizeHandle } from "../normalize-handle" + +describe("normalizeHandle", function () { + it("should generate URL friendly handles", function () { + const expectations = [ + { + input: "The fan boy's club", + output: "the-fan-boys-club", + }, + { + input: "nouvelles années", + output: "nouvelles-annees", + }, + { + input: "25% OFF", + output: "25-off", + }, + { + input: "25% de réduction", + output: "25-de-reduction", + }, + { + input: "-first-product", + output: "-first-product", + }, + { + input: "user.product", + output: "userproduct", + }, + { + input: "_first-product", + output: "-first-product", + }, + { + input: "_HELLO_WORLD", + output: "-hello-world", + }, + ] + + expectations.forEach((expectation) => { + expect(normalizeHandle(expectation.input)).toEqual(expectation.output) + }) + }) +}) diff --git a/packages/core/utils/src/common/index.ts b/packages/core/utils/src/common/index.ts index 2b795e89d073f..9bbd3cafa3306 100644 --- a/packages/core/utils/src/common/index.ts +++ b/packages/core/utils/src/common/index.ts @@ -58,3 +58,5 @@ export * from "./transaction" export * from "./trim-zeros" export * from "./upper-case-first" export * from "./wrap-handler" +export * from "./normalize-handle" +export * from "./validate-handle" diff --git a/packages/core/utils/src/common/normalize-handle.ts b/packages/core/utils/src/common/normalize-handle.ts new file mode 100644 index 0000000000000..304aa788864e9 --- /dev/null +++ b/packages/core/utils/src/common/normalize-handle.ts @@ -0,0 +1,18 @@ +import { kebabCase } from "./to-kebab-case" + +/** + * Helper method to normalize entity "handle" to be URL + * friendly. + * + * - Works by converting the value to lowercase + * - Splits and remove accents from characters + * - Removes all unallowed characters like a '"%$ and so on. + */ +export const normalizeHandle = (value: string): string => { + return kebabCase( + value + .toLowerCase() + .normalize("NFD") + .replace(/[\u0300-\u036f]/g, "") + ).replace(/[^a-z0-9A-Z-_]/g, "") +} diff --git a/packages/core/utils/src/common/to-kebab-case.ts b/packages/core/utils/src/common/to-kebab-case.ts index 6b42ee3985992..5a975cd035762 100644 --- a/packages/core/utils/src/common/to-kebab-case.ts +++ b/packages/core/utils/src/common/to-kebab-case.ts @@ -1,4 +1,4 @@ -export const kebabCase = (string) => +export const kebabCase = (string: string): string => string .replace(/([a-z])([A-Z])/g, "$1-$2") .replace(/[\s_]+/g, "-") diff --git a/packages/core/utils/src/common/validate-handle.ts b/packages/core/utils/src/common/validate-handle.ts new file mode 100644 index 0000000000000..01edd607a35aa --- /dev/null +++ b/packages/core/utils/src/common/validate-handle.ts @@ -0,0 +1,9 @@ +import { kebabCase } from "./to-kebab-case" + +/** + * Helper method to validate entity "handle" to be URL + * friendly. + */ +export const isValidHandle = (value: string): boolean => { + return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value) +} From 5cbbd4e756baeca9bce613967d5d7fb5ffc2f85f Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Mon, 13 May 2024 20:57:14 +0530 Subject: [PATCH 05/12] feat: move handle validation to product updation --- ...malize-handle.spec.ts => to-handle.spec.ts} | 4 ++-- packages/core/utils/src/common/index.ts | 2 +- .../{normalize-handle.ts => to-handle.ts} | 6 +++--- .../src/api-v2/admin/collections/validators.ts | 5 ++--- .../src/api-v2/admin/products/validators.ts | 7 ++----- .../api-v2/utils/common-validators/common.ts | 8 -------- packages/modules/product/src/models/product.ts | 4 ++-- .../src/services/product-module-service.ts | 18 +++++++++++++++--- 8 files changed, 27 insertions(+), 27 deletions(-) rename packages/core/utils/src/common/__tests__/{normalize-handle.spec.ts => to-handle.spec.ts} (86%) rename packages/core/utils/src/common/{normalize-handle.ts => to-handle.ts} (73%) diff --git a/packages/core/utils/src/common/__tests__/normalize-handle.spec.ts b/packages/core/utils/src/common/__tests__/to-handle.spec.ts similarity index 86% rename from packages/core/utils/src/common/__tests__/normalize-handle.spec.ts rename to packages/core/utils/src/common/__tests__/to-handle.spec.ts index b5293ccd67b1c..ddc18692475a5 100644 --- a/packages/core/utils/src/common/__tests__/normalize-handle.spec.ts +++ b/packages/core/utils/src/common/__tests__/to-handle.spec.ts @@ -1,4 +1,4 @@ -import { normalizeHandle } from "../normalize-handle" +import { toHandle } from "../to-handle" describe("normalizeHandle", function () { it("should generate URL friendly handles", function () { @@ -38,7 +38,7 @@ describe("normalizeHandle", function () { ] expectations.forEach((expectation) => { - expect(normalizeHandle(expectation.input)).toEqual(expectation.output) + expect(toHandle(expectation.input)).toEqual(expectation.output) }) }) }) diff --git a/packages/core/utils/src/common/index.ts b/packages/core/utils/src/common/index.ts index 9bbd3cafa3306..4fc71a54c15ac 100644 --- a/packages/core/utils/src/common/index.ts +++ b/packages/core/utils/src/common/index.ts @@ -58,5 +58,5 @@ export * from "./transaction" export * from "./trim-zeros" export * from "./upper-case-first" export * from "./wrap-handler" -export * from "./normalize-handle" +export * from "./to-handle" export * from "./validate-handle" diff --git a/packages/core/utils/src/common/normalize-handle.ts b/packages/core/utils/src/common/to-handle.ts similarity index 73% rename from packages/core/utils/src/common/normalize-handle.ts rename to packages/core/utils/src/common/to-handle.ts index 304aa788864e9..56800cd2c43c1 100644 --- a/packages/core/utils/src/common/normalize-handle.ts +++ b/packages/core/utils/src/common/to-handle.ts @@ -1,14 +1,14 @@ import { kebabCase } from "./to-kebab-case" /** - * Helper method to normalize entity "handle" to be URL - * friendly. + * Helper method to create a to be URL friendly "handle" from + * a string value. * * - Works by converting the value to lowercase * - Splits and remove accents from characters * - Removes all unallowed characters like a '"%$ and so on. */ -export const normalizeHandle = (value: string): string => { +export const toHandle = (value: string): string => { return kebabCase( value .toLowerCase() diff --git a/packages/medusa/src/api-v2/admin/collections/validators.ts b/packages/medusa/src/api-v2/admin/collections/validators.ts index 6088451f0fb17..687d6e8a1c04a 100644 --- a/packages/medusa/src/api-v2/admin/collections/validators.ts +++ b/packages/medusa/src/api-v2/admin/collections/validators.ts @@ -1,4 +1,3 @@ -import { HandleValidator } from "../../utils/common-validators" import { createFindParams, createOperatorMap, @@ -30,13 +29,13 @@ export const AdminGetCollectionsParams = createFindParams({ export type AdminCreateCollectionType = z.infer export const AdminCreateCollection = z.object({ title: z.string(), - handle: HandleValidator, + handle: z.string().optional(), metadata: z.record(z.unknown()).optional(), }) export type AdminUpdateCollectionType = z.infer export const AdminUpdateCollection = z.object({ title: z.string().optional(), - handle: HandleValidator, + handle: z.string().optional(), metadata: z.record(z.unknown()).optional(), }) diff --git a/packages/medusa/src/api-v2/admin/products/validators.ts b/packages/medusa/src/api-v2/admin/products/validators.ts index 93884b0cd547c..5a2b2b006cdd9 100644 --- a/packages/medusa/src/api-v2/admin/products/validators.ts +++ b/packages/medusa/src/api-v2/admin/products/validators.ts @@ -1,9 +1,6 @@ import { ProductStatus } from "@medusajs/utils" import { z } from "zod" -import { - GetProductsParams, - HandleValidator, -} from "../../utils/common-validators" +import { GetProductsParams } from "../../utils/common-validators" import { createFindParams, createOperatorMap, @@ -180,7 +177,7 @@ export const AdminCreateProduct = z discountable: z.boolean().optional().default(true), images: z.array(z.object({ url: z.string() })).optional(), thumbnail: z.string().optional(), - handle: HandleValidator, + handle: z.string().optional(), status: statusEnum.optional().default(ProductStatus.DRAFT), type_id: z.string().nullable().optional(), collection_id: z.string().nullable().optional(), diff --git a/packages/medusa/src/api-v2/utils/common-validators/common.ts b/packages/medusa/src/api-v2/utils/common-validators/common.ts index 14f984158e777..f8506390e905b 100644 --- a/packages/medusa/src/api-v2/utils/common-validators/common.ts +++ b/packages/medusa/src/api-v2/utils/common-validators/common.ts @@ -36,11 +36,3 @@ export const OptionalBooleanValidator = z.preprocess( (val: any) => optionalBooleanMapper.get(val?.toLowerCase()), z.boolean().optional() ) - -/** - * Validates entity handle to have URL-safe characters - */ -export const HandleValidator = z - .string() - .regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/) - .optional() diff --git a/packages/modules/product/src/models/product.ts b/packages/modules/product/src/models/product.ts index 46e72ecf288d4..a6875141ef8bd 100644 --- a/packages/modules/product/src/models/product.ts +++ b/packages/modules/product/src/models/product.ts @@ -17,9 +17,9 @@ import { createPsqlIndexStatementHelper, DALUtils, generateEntityId, - kebabCase, ProductUtils, Searchable, + toHandle, } from "@medusajs/utils" import ProductCategory from "./product-category" import ProductCollection from "./product-collection" @@ -216,7 +216,7 @@ class Product { this.collection_id ??= this.collection?.id ?? null if (!this.handle && this.title) { - this.handle = kebabCase(this.title) + this.handle = toHandle(this.title) } } } diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index f7723e938d264..c904f26e43208 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -32,6 +32,7 @@ import { ProductStatus, promiseAll, removeUndefined, + isValidHandle, } from "@medusajs/utils" import { ProductCategoryEventData, @@ -1315,9 +1316,13 @@ export default class ProductModuleService< sharedContext )) as ProductTypes.CreateProductDTO - if (!productData.handle && productData.title) { - productData.handle = kebabCase(productData.title) - } + /** + * We are already computing handle from title in model. Do we + * need here again? + */ + // if (!productData.handle && productData.title) { + // productData.handle = kebabCase(productData.title) + // } if (!productData.status) { productData.status = ProductStatus.DRAFT @@ -1339,6 +1344,13 @@ export default class ProductModuleService< productData.discountable = false } + if (productData.handle && !isValidHandle(productData.handle)) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Invalid product handle. It must contain URL safe characters" + ) + } + if (productData.tags?.length && productData.tags.some((t) => !t.id)) { const dbTags = await this.productTagService_.list( { From 652fbe41ef6812d39a5be9cbac1e8ecdcf809c45 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 14 May 2024 11:20:53 +0530 Subject: [PATCH 06/12] refactor: remove title to handle generation of product from model --- packages/modules/product/src/models/product.ts | 7 +++---- .../product/src/services/product-module-service.ts | 10 +++------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/modules/product/src/models/product.ts b/packages/modules/product/src/models/product.ts index a6875141ef8bd..77562cae67edc 100644 --- a/packages/modules/product/src/models/product.ts +++ b/packages/modules/product/src/models/product.ts @@ -19,7 +19,6 @@ import { generateEntityId, ProductUtils, Searchable, - toHandle, } from "@medusajs/utils" import ProductCategory from "./product-category" import ProductCollection from "./product-collection" @@ -215,9 +214,9 @@ class Product { this.type_id ??= this.type?.id ?? null this.collection_id ??= this.collection?.id ?? null - if (!this.handle && this.title) { - this.handle = toHandle(this.title) - } + // if (!this.handle && this.title) { + // this.handle = toHandle(this.title) + // } } } diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index c904f26e43208..7243619588aad 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -1316,13 +1316,9 @@ export default class ProductModuleService< sharedContext )) as ProductTypes.CreateProductDTO - /** - * We are already computing handle from title in model. Do we - * need here again? - */ - // if (!productData.handle && productData.title) { - // productData.handle = kebabCase(productData.title) - // } + if (!productData.handle && productData.title) { + productData.handle = kebabCase(productData.title) + } if (!productData.status) { productData.status = ProductStatus.DRAFT From 9d86eece9b57d8772591cff1852df20bc1f18d56 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 14 May 2024 11:40:09 +0530 Subject: [PATCH 07/12] refactor: use toHandle method to generate product handle --- .../modules/product/src/services/product-module-service.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index 7243619588aad..503e510279278 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -33,6 +33,7 @@ import { promiseAll, removeUndefined, isValidHandle, + toHandle, } from "@medusajs/utils" import { ProductCategoryEventData, @@ -1317,7 +1318,7 @@ export default class ProductModuleService< )) as ProductTypes.CreateProductDTO if (!productData.handle && productData.title) { - productData.handle = kebabCase(productData.title) + productData.handle = toHandle(productData.title) } if (!productData.status) { From 4c8a9372ee96c9c36ece281dc837c34090690e4e Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 14 May 2024 15:22:35 +0530 Subject: [PATCH 08/12] refactor: add back handle generation within model --- packages/modules/product/src/models/product.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/modules/product/src/models/product.ts b/packages/modules/product/src/models/product.ts index 77562cae67edc..a6875141ef8bd 100644 --- a/packages/modules/product/src/models/product.ts +++ b/packages/modules/product/src/models/product.ts @@ -19,6 +19,7 @@ import { generateEntityId, ProductUtils, Searchable, + toHandle, } from "@medusajs/utils" import ProductCategory from "./product-category" import ProductCollection from "./product-collection" @@ -214,9 +215,9 @@ class Product { this.type_id ??= this.type?.id ?? null this.collection_id ??= this.collection?.id ?? null - // if (!this.handle && this.title) { - // this.handle = toHandle(this.title) - // } + if (!this.handle && this.title) { + this.handle = toHandle(this.title) + } } } From 39391ef622c2cd5807b25accd90c2292eb11151d Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 14 May 2024 17:24:00 +0530 Subject: [PATCH 09/12] refactor: abstract validation logic to its own function --- .../src/services/product-module-service.ts | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index 503e510279278..74ff346950cc7 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -1308,6 +1308,21 @@ export default class ProductModuleService< return productData } + /** + * Validates the manually provided handle value of the product + * to be URL-safe + */ + protected validateProductHandle( + productData: UpdateProductInput | ProductTypes.CreateProductDTO + ) { + if (productData.handle && !isValidHandle(productData.handle)) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + "Invalid product handle. It must contain URL safe characters" + ) + } + } + protected async normalizeCreateProductInput( product: ProductTypes.CreateProductDTO, @MedusaContext() sharedContext: Context = {} @@ -1341,12 +1356,7 @@ export default class ProductModuleService< productData.discountable = false } - if (productData.handle && !isValidHandle(productData.handle)) { - throw new MedusaError( - MedusaError.Types.INVALID_DATA, - "Invalid product handle. It must contain URL safe characters" - ) - } + this.validateProductHandle(product) if (productData.tags?.length && productData.tags.some((t) => !t.id)) { const dbTags = await this.productTagService_.list( From 2f41fc95e8588f96f3cd55130d163ceab2a0188d Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Tue, 14 May 2024 18:28:12 +0530 Subject: [PATCH 10/12] refactor: rename validateProductHandle to validateProductPayload --- .../src/services/product-module-service.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index 74ff346950cc7..479d7d354581e 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -1167,9 +1167,10 @@ export default class ProductModuleService< @MedusaContext() sharedContext: Context = {} ): Promise { const normalizedInput = await promiseAll( - data.map( - async (d) => await this.normalizeCreateProductInput(d, sharedContext) - ) + data.map(async (d) => { + await this.normalizeCreateProductInput(d, sharedContext) + this.validateProductPayload(d) + }) ) const productData = await this.productService_.upsertWithReplace( @@ -1225,9 +1226,10 @@ export default class ProductModuleService< @MedusaContext() sharedContext: Context = {} ): Promise { const normalizedInput = await promiseAll( - data.map( - async (d) => await this.normalizeUpdateProductInput(d, sharedContext) - ) + data.map(async (d) => { + await this.normalizeUpdateProductInput(d, sharedContext) + this.validateProductPayload(d) + }) ) const productData = await this.productService_.upsertWithReplace( @@ -1312,7 +1314,7 @@ export default class ProductModuleService< * Validates the manually provided handle value of the product * to be URL-safe */ - protected validateProductHandle( + protected validateProductPayload( productData: UpdateProductInput | ProductTypes.CreateProductDTO ) { if (productData.handle && !isValidHandle(productData.handle)) { @@ -1356,8 +1358,6 @@ export default class ProductModuleService< productData.discountable = false } - this.validateProductHandle(product) - if (productData.tags?.length && productData.tags.some((t) => !t.id)) { const dbTags = await this.productTagService_.list( { From e8a726a13c6201920f5fdb96231420637fc08653 Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 15 May 2024 14:00:32 +0530 Subject: [PATCH 11/12] fix: return mapped input --- packages/modules/product/src/services/product-module-service.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index 479d7d354581e..65004be44b129 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -1170,6 +1170,7 @@ export default class ProductModuleService< data.map(async (d) => { await this.normalizeCreateProductInput(d, sharedContext) this.validateProductPayload(d) + return d }) ) @@ -1229,6 +1230,7 @@ export default class ProductModuleService< data.map(async (d) => { await this.normalizeUpdateProductInput(d, sharedContext) this.validateProductPayload(d) + return d }) ) From 83be029ba7534443192b09da4df54b42d3ba716d Mon Sep 17 00:00:00 2001 From: Harminder Virk Date: Wed, 15 May 2024 15:04:41 +0530 Subject: [PATCH 12/12] fix: return mutated values when mapping over products during create+update --- .../src/services/product-module-service.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/modules/product/src/services/product-module-service.ts b/packages/modules/product/src/services/product-module-service.ts index 65004be44b129..5ce5ba0be40ad 100644 --- a/packages/modules/product/src/services/product-module-service.ts +++ b/packages/modules/product/src/services/product-module-service.ts @@ -1168,9 +1168,12 @@ export default class ProductModuleService< ): Promise { const normalizedInput = await promiseAll( data.map(async (d) => { - await this.normalizeCreateProductInput(d, sharedContext) - this.validateProductPayload(d) - return d + const normalized = await this.normalizeCreateProductInput( + d, + sharedContext + ) + this.validateProductPayload(normalized) + return normalized }) ) @@ -1228,9 +1231,12 @@ export default class ProductModuleService< ): Promise { const normalizedInput = await promiseAll( data.map(async (d) => { - await this.normalizeUpdateProductInput(d, sharedContext) - this.validateProductPayload(d) - return d + const normalized = await this.normalizeUpdateProductInput( + d, + sharedContext + ) + this.validateProductPayload(normalized) + return normalized }) )