Browse Source

fix(core): Ignore deleted products when checking slug uniqueness

Fixes #558

BREAKING CHANGE: The ProductTranslation entity has had a constraint removed, requiring a schema
migration.
Michael Bromley 5 years ago
parent
commit
844a12d6ba

+ 0 - 2
packages/core/e2e/order-modification.e2e-spec.ts

@@ -2,7 +2,6 @@
 import { omit } from '@vendure/common/lib/omit';
 import { pick } from '@vendure/common/lib/pick';
 import {
-    DefaultLogger,
     defaultShippingCalculator,
     defaultShippingEligibilityChecker,
     mergeConfig,
@@ -82,7 +81,6 @@ const testCalculator = new ShippingCalculator({
 describe('Order modification', () => {
     const { server, adminClient, shopClient } = createTestEnvironment(
         mergeConfig(testConfig, {
-            logger: new DefaultLogger(),
             paymentOptions: {
                 paymentMethodHandlers: [testSuccessfulPaymentMethod],
             },

+ 20 - 0
packages/core/e2e/product.e2e-spec.ts

@@ -1166,6 +1166,26 @@ describe('Product resolver', () => {
                 `No Product with the id '1' could be found`,
             ),
         );
+
+        // https://github.com/vendure-ecommerce/vendure/issues/558
+        it('slug of a deleted product can be re-used', async () => {
+            const result = await adminClient.query<CreateProduct.Mutation, CreateProduct.Variables>(
+                CREATE_PRODUCT,
+                {
+                    input: {
+                        translations: [
+                            {
+                                languageCode: LanguageCode.en,
+                                name: 'Product reusing deleted slug',
+                                slug: productToDelete.slug,
+                                description: 'stuff',
+                            },
+                        ],
+                    },
+                },
+            );
+            expect(result.createProduct.slug).toBe(productToDelete.slug);
+        });
     });
 });
 

+ 0 - 1
packages/core/src/entity/product/product-translation.entity.ts

@@ -10,7 +10,6 @@ import { CustomProductFieldsTranslation } from '../custom-entity-fields';
 import { Product } from './product.entity';
 
 @Entity()
-@Index(['languageCode', 'slug'], { unique: true })
 export class ProductTranslation extends VendureEntity implements Translation<Product>, HasCustomFields {
     constructor(input?: DeepPartial<Translation<Product>>) {
         super(input);

+ 4 - 2
packages/core/src/service/helpers/slug-validator/slug-validator.ts

@@ -21,6 +21,7 @@ export type TranslationEntity = VendureEntity & {
     id: ID;
     languageCode: LanguageCode;
     slug: string;
+    base: any;
 };
 
 @Injectable()
@@ -47,6 +48,7 @@ export class SlugValidator {
                         const qb = this.connection
                             .getRepository(ctx, translationEntity)
                             .createQueryBuilder('translation')
+                            .innerJoinAndSelect('translation.base', 'base')
                             .where(`translation.slug = :slug`, { slug: t.slug })
                             .andWhere(`translation.languageCode = :languageCode`, {
                                 languageCode: t.languageCode,
@@ -55,7 +57,7 @@ export class SlugValidator {
                             qb.andWhere(`translation.base != :id`, { id: input.id });
                         }
                         match = await qb.getOne();
-                        if (match) {
+                        if (match && !match.base.deletedAt) {
                             suffix++;
                             if (alreadySuffixed.test(t.slug)) {
                                 t.slug = t.slug.replace(alreadySuffixed, `-${suffix}`);
@@ -63,7 +65,7 @@ export class SlugValidator {
                                 t.slug = `${t.slug}-${suffix}`;
                             }
                         }
-                    } while (match);
+                    } while (match && !match.base.deletedAt);
                 }
             }
         }