فهرست منبع

feat(core): Make slug unique per channel instead of globally unique (#1729)

Martijn 3 سال پیش
والد
کامیت
ac1dcc75e9
2فایلهای تغییر یافته به همراه80 افزوده شده و 28 حذف شده
  1. 76 27
      packages/core/e2e/collection.e2e-spec.ts
  2. 4 1
      packages/core/src/service/helpers/slug-validator/slug-validator.ts

+ 76 - 27
packages/core/e2e/collection.e2e-spec.ts

@@ -5,7 +5,7 @@ import {
     facetValueCollectionFilter,
     variantNameCollectionFilter,
 } from '@vendure/core';
-import { createTestEnvironment } from '@vendure/testing';
+import { createTestEnvironment, E2E_DEFAULT_CHANNEL_TOKEN } from '@vendure/testing';
 import gql from 'graphql-tag';
 import path from 'path';
 
@@ -17,9 +17,11 @@ import { productIdCollectionFilter, variantIdCollectionFilter } from '../src/ind
 import { COLLECTION_FRAGMENT, FACET_VALUE_FRAGMENT } from './graphql/fragments';
 import {
     Collection,
+    CreateChannel,
     CreateCollection,
     CreateCollectionInput,
     CreateCollectionSelectVariants,
+    CurrencyCode,
     DeleteCollection,
     DeleteProduct,
     DeleteProductVariant,
@@ -47,6 +49,7 @@ import {
     UpdateProductVariants,
 } from './graphql/generated-e2e-admin-types';
 import {
+    CREATE_CHANNEL,
     CREATE_COLLECTION,
     DELETE_PRODUCT,
     DELETE_PRODUCT_VARIANT,
@@ -74,6 +77,7 @@ describe('Collection resolver', () => {
     let electronicsBreadcrumbsCollection: Collection.Fragment;
     let computersBreadcrumbsCollection: Collection.Fragment;
     let pearBreadcrumbsCollection: Collection.Fragment;
+    const SECOND_CHANNEL_TOKEN = 'second_channel_token';
 
     beforeAll(async () => {
         await server.init({
@@ -98,6 +102,17 @@ describe('Collection resolver', () => {
             (values, facet) => [...values, ...facet.values],
             [] as FacetValueFragment[],
         );
+        await adminClient.query<CreateChannel.Mutation, CreateChannel.Variables>(CREATE_CHANNEL, {
+            input: {
+                code: 'second-channel',
+                token: SECOND_CHANNEL_TOKEN,
+                defaultLanguageCode: LanguageCode.en,
+                currencyCode: CurrencyCode.USD,
+                pricesIncludeTax: true,
+                defaultShippingZoneId: 'T_1',
+                defaultTaxZoneId: 'T_1',
+            },
+        });
     }, TEST_SETUP_TIMEOUT_MS);
 
     afterAll(async () => {
@@ -285,7 +300,41 @@ describe('Collection resolver', () => {
                 'zubehor-2',
             );
         });
-        it('creates a root collection to became a 1st level collection later #779', async () => {
+
+        it('creates the duplicate slug without suffix in another channel', async () => {
+            adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
+            const { createCollection } = await adminClient.query<
+                CreateCollection.Mutation,
+                CreateCollection.Variables
+            >(CREATE_COLLECTION, {
+                input: {
+                    translations: [
+                        {
+                            languageCode: LanguageCode.en,
+                            name: 'Accessories',
+                            description: '',
+                            slug: 'Accessories',
+                        },
+                        {
+                            languageCode: LanguageCode.de,
+                            name: 'Zubehör',
+                            description: '',
+                            slug: 'Zubehör',
+                        },
+                    ],
+                    filters: [],
+                },
+            });
+            expect(createCollection.translations.find(t => t.languageCode === LanguageCode.en)?.slug).toBe(
+                'accessories',
+            );
+            expect(createCollection.translations.find(t => t.languageCode === LanguageCode.de)?.slug).toBe(
+                'zubehor',
+            );
+        });
+
+        it('creates a root collection to become a 1st level collection later #779', async () => {
+            adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
             const result = await adminClient.query<CreateCollection.Mutation, CreateCollection.Variables>(
                 CREATE_COLLECTION,
                 {
@@ -538,9 +587,17 @@ describe('Collection resolver', () => {
             expect(product?.collections.sort(sortById)).toEqual([
                 {
                     id: 'T_10',
+                    name: 'Electronics Breadcrumbs',
+                    parent: {
+                        id: 'T_1',
+                        name: '__root_collection__',
+                    },
+                },
+                {
+                    id: 'T_11',
                     name: 'Pear Breadcrumbs',
                     parent: {
-                        id: 'T_8',
+                        id: 'T_9',
                         name: 'Computers Breadcrumbs',
                     },
                 },
@@ -568,17 +625,9 @@ describe('Collection resolver', () => {
                         name: 'Computers',
                     },
                 },
-                {
-                    id: 'T_8',
-                    name: 'Computers Breadcrumbs',
-                    parent: {
-                        id: 'T_1',
-                        name: '__root_collection__',
-                    },
-                },
                 {
                     id: 'T_9',
-                    name: 'Electronics Breadcrumbs',
+                    name: 'Computers Breadcrumbs',
                     parent: {
                         id: 'T_1',
                         name: '__root_collection__',
@@ -875,7 +924,7 @@ describe('Collection resolver', () => {
                 fail(`did not return the collection`);
                 return;
             }
-            expect(result.collection.children?.map(c => (c as any).position)).toEqual([0, 1, 2, 3, 4, 5]);
+            expect(result.collection.children?.map(c => (c as any).position)).toEqual([0, 1, 2, 3, 4, 5, 6]);
         });
 
         async function getChildrenOf(parentId: string): Promise<Array<{ name: string; id: string }>> {
@@ -994,23 +1043,23 @@ describe('Collection resolver', () => {
                     name: 'Pear',
                 },
                 {
-                    id: 'T_8',
+                    id: 'T_9',
                     name: 'Computers Breadcrumbs',
                 },
                 {
-                    id: 'T_9',
+                    id: 'T_10',
                     name: 'Electronics Breadcrumbs',
                 },
                 {
-                    id: 'T_10',
+                    id: 'T_11',
                     name: 'Pear Breadcrumbs',
                 },
                 {
-                    id: 'T_11',
+                    id: 'T_12',
                     name: 'Delete Me Parent',
                 },
                 {
-                    id: 'T_12',
+                    id: 'T_13',
                     name: 'Delete Me Child',
                 },
             ]);
@@ -1060,15 +1109,15 @@ describe('Collection resolver', () => {
                 { id: 'T_4', name: 'Computers' },
                 { id: 'T_5', name: 'Pear' },
                 {
-                    id: 'T_8',
+                    id: 'T_9',
                     name: 'Computers Breadcrumbs',
                 },
                 {
-                    id: 'T_9',
+                    id: 'T_10',
                     name: 'Electronics Breadcrumbs',
                 },
                 {
-                    id: 'T_10',
+                    id: 'T_11',
                     name: 'Pear Breadcrumbs',
                 },
             ]);
@@ -1877,27 +1926,27 @@ describe('Collection resolver', () => {
                     name: 'Pear',
                 },
                 {
-                    id: 'T_9',
+                    id: 'T_10',
                     name: 'Electronics Breadcrumbs',
                 },
                 {
-                    id: 'T_14',
+                    id: 'T_15',
                     name: 'Photo AND Pear',
                 },
                 {
-                    id: 'T_15',
+                    id: 'T_16',
                     name: 'Photo OR Pear',
                 },
                 {
-                    id: 'T_17',
+                    id: 'T_18',
                     name: 'contains camera',
                 },
                 {
-                    id: 'T_19',
+                    id: 'T_20',
                     name: 'endsWith camera',
                 },
                 {
-                    id: 'T_25',
+                    id: 'T_26',
                     name: 'pear electronics',
                 },
             ]);

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

@@ -5,6 +5,7 @@ import { ID, Type } from '@vendure/common/lib/shared-types';
 
 import { RequestContext } from '../../../api/common/request-context';
 import { TransactionalConnection } from '../../../connection/transactional-connection';
+import { Collection, Product } from '../../../entity';
 import { VendureEntity } from '../../../entity/base/base.entity';
 import { ProductOptionGroup } from '../../../entity/product-option-group/product-option-group.entity';
 
@@ -66,7 +67,9 @@ export class SlugValidator {
                             .getRepository(ctx, translationEntity)
                             .createQueryBuilder('translation')
                             .innerJoinAndSelect('translation.base', 'base')
-                            .where(`translation.slug = :slug`, { slug: t.slug })
+                            .innerJoinAndSelect('base.channels', 'channel')
+                            .where(`channel.id = :channelId`, { channelId: ctx.channelId })
+                            .andWhere(`translation.slug = :slug`, { slug: t.slug })
                             .andWhere(`translation.languageCode = :languageCode`, {
                                 languageCode: t.languageCode,
                             });