Parcourir la source

Merge branch 'minor' into major

Michael Bromley il y a 3 ans
Parent
commit
4d7473d8eb
26 fichiers modifiés avec 226 ajouts et 43 suppressions
  1. 21 0
      CHANGELOG.md
  2. 6 2
      docs/content/developer-guide/customizing-models.md
  3. 6 1
      packages/admin-ui/src/lib/catalog/src/components/option-value-input/option-value-input.component.ts
  4. 22 0
      packages/admin-ui/src/lib/core/src/common/utilities/configurable-operation-utils.spec.ts
  5. 7 2
      packages/admin-ui/src/lib/core/src/common/utilities/configurable-operation-utils.ts
  6. 3 0
      packages/admin-ui/src/lib/core/src/data/utils/remove-readonly-custom-fields.ts
  7. 9 0
      packages/admin-ui/src/lib/core/src/shared/pipes/locale-currency.pipe.spec.ts
  8. 8 3
      packages/admin-ui/src/lib/core/src/shared/pipes/locale-currency.pipe.ts
  9. 1 10
      packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.html
  10. 3 2
      packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.ts
  11. 17 17
      packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.html
  12. 4 0
      packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.scss
  13. 9 1
      packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.ts
  14. 1 0
      packages/admin-ui/src/lib/static/styles/_variables.scss
  15. 1 1
      packages/admin-ui/src/lib/static/styles/global/_sass-overrides.scss
  16. 33 0
      packages/core/e2e/customer-group.e2e-spec.ts
  17. 34 0
      packages/core/e2e/default-search-plugin.e2e-spec.ts
  18. 1 1
      packages/core/src/api/config/generate-permissions.ts
  19. 11 0
      packages/core/src/api/decorators/allow.decorator.ts
  20. 13 1
      packages/core/src/api/resolvers/admin/customer.resolver.ts
  21. 1 0
      packages/core/src/api/schema/common/order.type.graphql
  22. 3 0
      packages/core/src/plugin/default-search-plugin/fulltext-search.service.ts
  23. 6 1
      packages/core/src/plugin/default-search-plugin/search-strategy/mysql-search-strategy.ts
  24. 1 1
      packages/core/src/plugin/default-search-plugin/search-strategy/postgres-search-strategy.ts
  25. 1 0
      packages/core/src/service/services/customer-group.service.ts
  26. 4 0
      packages/create/templates/vendure-config.hbs

+ 21 - 0
CHANGELOG.md

@@ -1,3 +1,24 @@
+## <small>1.7.2 (2022-09-19)</small>
+
+
+#### Fixes
+
+* **admin-ui** Correctly parse configurable args when not edited ([f753f76](https://github.com/vendure-ecommerce/vendure/commit/f753f76)), closes [#1786](https://github.com/vendure-ecommerce/vendure/issues/1786)
+* **admin-ui** Do not allow duplicate option names ([4c4ad29](https://github.com/vendure-ecommerce/vendure/commit/4c4ad29)), closes [#1747](https://github.com/vendure-ecommerce/vendure/issues/1747)
+* **admin-ui** Fix alignment of breadcrumbs ([3584ef9](https://github.com/vendure-ecommerce/vendure/commit/3584ef9))
+* **admin-ui** Fix error when modifying Order with custom field relation ([eace1c1](https://github.com/vendure-ecommerce/vendure/commit/eace1c1)), closes [#1792](https://github.com/vendure-ecommerce/vendure/issues/1792)
+* **admin-ui** Fix error when using non-standard currencyCode ([4466b24](https://github.com/vendure-ecommerce/vendure/commit/4466b24)), closes [#1768](https://github.com/vendure-ecommerce/vendure/issues/1768)
+* **admin-ui** Fix refresh issues with customer group list view ([04b431c](https://github.com/vendure-ecommerce/vendure/commit/04b431c))
+* **core** Add missing driver options in DefaultSearchPlugin ([12e2807](https://github.com/vendure-ecommerce/vendure/commit/12e2807))
+* **core** Correctly escape search term for mysql strategy ([2fa7fcf](https://github.com/vendure-ecommerce/vendure/commit/2fa7fcf)), closes [#1789](https://github.com/vendure-ecommerce/vendure/issues/1789)
+* **core** Correctly escape search term for postgres strategy ([ec70228](https://github.com/vendure-ecommerce/vendure/commit/ec70228)), closes [#1789](https://github.com/vendure-ecommerce/vendure/issues/1789)
+* **core** Correctly populate shipping/billing address for new customer ([264b326](https://github.com/vendure-ecommerce/vendure/commit/264b326))
+* **core** Handle edge-case of Collection.breadcrumbs having null values ([4a9ec5c](https://github.com/vendure-ecommerce/vendure/commit/4a9ec5c))
+* **core** Include missing id field in ShippingLine type ([481d0de](https://github.com/vendure-ecommerce/vendure/commit/481d0de)), closes [#1792](https://github.com/vendure-ecommerce/vendure/issues/1792)
+* **core** Remove deleted Customers from any CustomerGroups ([9820d9e](https://github.com/vendure-ecommerce/vendure/commit/9820d9e)), closes [#1785](https://github.com/vendure-ecommerce/vendure/issues/1785)
+* **create** Fix default migration path of scaffold (#1759) ([e1c90cc](https://github.com/vendure-ecommerce/vendure/commit/e1c90cc)), closes [#1759](https://github.com/vendure-ecommerce/vendure/issues/1759)
+* **create** Make dotenv a dependency, not devDependency ([a641beb](https://github.com/vendure-ecommerce/vendure/commit/a641beb))
+
 ## <small>1.7.1 (2022-08-29)</small>
 
 

+ 6 - 2
docs/content/developer-guide/customizing-models.md

@@ -204,9 +204,13 @@ However, this sacrifices type safety. To make our custom fields type-safe we can
 
 ```TypeScript
 // types.ts
-import { CustomProductFields } from '@vendure/core';
 
-declare module '@vendure/core' {
+// Note: we are using deep a import here, rather than importing from `@vendure/core` due to 
+// a possible bug in TypeScript (https://github.com/microsoft/TypeScript/issues/46617) which
+// causes issues when multiple plugins extend the same custom fields interface.
+import { CustomProductFields } from '@vendure/core/dist/entity/custom-entity-fields';
+
+declare module '@vendure/core/dist/entity/custom-entity-fields' {
   interface CustomProductFields {
     infoUrl: string;
     downloadable: boolean;

+ 6 - 1
packages/admin-ui/src/lib/catalog/src/components/option-value-input/option-value-input.component.ts

@@ -140,7 +140,12 @@ export class OptionValueInputComponent implements ControlValueAccessor {
     }
 
     private addOptionValue() {
-        const options = this.parseInputIntoOptions(this.input);
+        const options = this.parseInputIntoOptions(this.input).filter(option => {
+            // do not add an option with the same name
+            // as an existing option
+            const existing = this.options ?? this.formValue;
+            return !existing?.find(o => o?.name === option.name);
+        });
         if (!this.formValue && this.options) {
             for (const option of options) {
                 this.add.emit(option);

+ 22 - 0
packages/admin-ui/src/lib/core/src/common/utilities/configurable-operation-utils.spec.ts

@@ -143,4 +143,26 @@ describe('toConfigurableOperationInput()', () => {
             ],
         });
     });
+
+    it('works with array input', () => {
+        const value = toConfigurableOperationInput(configOp, {
+            args: [
+                { name: 'option2', value: 'value2-2' },
+                { name: 'option1', value: 'value1-2' },
+            ],
+        });
+        expect(value).toEqual({
+            code: 'test',
+            arguments: [
+                {
+                    name: 'option1',
+                    value: 'value1-2',
+                },
+                {
+                    name: 'option2',
+                    value: 'value2-2',
+                },
+            ],
+        });
+    });
 });

+ 7 - 2
packages/admin-ui/src/lib/core/src/common/utilities/configurable-operation-utils.ts

@@ -64,12 +64,17 @@ export function configurableDefinitionToInstance(
  */
 export function toConfigurableOperationInput(
     operation: ConfigurableOperation,
-    formValueOperations: { args: Record<string, any> },
+    formValueOperations: { args: Record<string, string> | Array<{ name: string; value: string }> },
 ): ConfigurableOperationInput {
+    const argsArray = Array.isArray(formValueOperations.args) ? formValueOperations.args : undefined;
+    const argsMap = !Array.isArray(formValueOperations.args) ? formValueOperations.args : undefined;
     return {
         code: operation.code,
         arguments: operation.args.map(({ name, value }, j) => {
-            const formValue = formValueOperations.args[name];
+            const formValue = argsArray?.find(arg => arg.name === name)?.value ?? argsMap?.[name];
+            if (!formValue) {
+                throw new Error(`Cannot find an argument value for the key "${name}"`);
+            }
             return {
                 name,
                 value: formValue?.hasOwnProperty('value')

+ 3 - 0
packages/admin-ui/src/lib/core/src/data/utils/remove-readonly-custom-fields.ts

@@ -32,6 +32,9 @@ export function isEntityCreateOrUpdateMutation(documentNode: DocumentNode): stri
             if (inputTypeName === 'UpdateActiveAdministratorInput') {
                 return 'Administrator';
             }
+            if (inputTypeName === 'ModifyOrderInput') {
+                return 'Order';
+            }
 
             const createMatch = inputTypeName.match(CREATE_ENTITY_REGEX);
             if (createMatch) {

+ 9 - 0
packages/admin-ui/src/lib/core/src/shared/pipes/locale-currency.pipe.spec.ts

@@ -16,4 +16,13 @@ describe('LocaleCurrencyPipe', () => {
         expect(pipe.transform(123, CurrencyCode.EUR, LanguageCode.de)).toBe('1,23 €');
         expect(pipe.transform(4200000, CurrencyCode.EUR, LanguageCode.de)).toBe('42.000,00 €');
     });
+
+    // https://github.com/vendure-ecommerce/vendure/issues/1768
+    it('Custom currency code in English', () => {
+        const pipe = new LocaleCurrencyPipe();
+        const customCurrencyCode = 'FLTH';
+        expect(pipe.transform(1, customCurrencyCode, LanguageCode.en)).toBe('0.01');
+        expect(pipe.transform(123, customCurrencyCode, LanguageCode.en)).toBe('1.23');
+        expect(pipe.transform(4200000, customCurrencyCode, LanguageCode.en)).toBe('42000.00');
+    });
 });

+ 8 - 3
packages/admin-ui/src/lib/core/src/shared/pipes/locale-currency.pipe.ts

@@ -30,9 +30,14 @@ export class LocaleCurrencyPipe extends LocaleBasePipe implements PipeTransform
         if (typeof value === 'number' && typeof currencyCode === 'string') {
             const activeLocale = this.getActiveLocale(locale);
             const majorUnits = value / 100;
-            return new Intl.NumberFormat(activeLocale, { style: 'currency', currency: currencyCode }).format(
-                majorUnits,
-            );
+            try {
+                return new Intl.NumberFormat(activeLocale, {
+                    style: 'currency',
+                    currency: currencyCode,
+                }).format(majorUnits);
+            } catch (e: any) {
+                return majorUnits.toFixed(2);
+            }
         }
         return value;
     }

+ 1 - 10
packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.html

@@ -50,7 +50,7 @@
                         {{ 'common.edit' | translate }}
                     </button>
                 </td>
-                <td [class.active]="group.id === activeGroupId">
+                <td [class.active]="group.id === activeGroupId" class="align-middle">
                     <vdr-dropdown>
                         <button type="button" class="btn btn-link btn-sm" vdrDropdownTrigger>
                             {{ 'common.actions' | translate }}
@@ -71,15 +71,6 @@
                 </td>
             </ng-template>
         </vdr-data-table>
-
-        <table class="table mt0" *ngIf="!(listIsEmpty$ | async); else emptyPlaceholder">
-            <tbody>
-                <tr
-                    *ngFor="let group of items$ | async"
-                    [class.active]="group.id === (activeGroup$ | async)?.id"
-                ></tr>
-            </tbody>
-        </table>
     </div>
     <ng-template #emptyPlaceholder>
         <vdr-empty-placeholder></vdr-empty-placeholder>

+ 3 - 2
packages/admin-ui/src/lib/customer/src/components/customer-group-list/customer-group-list.component.ts

@@ -143,11 +143,10 @@ export class CustomerGroupListComponent
                         ? this.dataService.customer.createCustomerGroup({ ...result, customerIds: [] })
                         : EMPTY,
                 ),
-                // refresh list
-                switchMap(() => this.dataService.customer.getCustomerGroupList().single$),
             )
             .subscribe(
                 () => {
+                    this.refresh();
                     this.notificationService.success(_('common.notify-create-success'), {
                         entity: 'CustomerGroup',
                     });
@@ -190,6 +189,7 @@ export class CustomerGroupListComponent
                     if (typeof result.errorMessage === 'string') {
                         this.notificationService.error(result.errorMessage);
                     } else {
+                        this.refresh();
                         this.notificationService.success(_('common.notify-delete-success'), {
                             entity: 'CustomerGroup',
                         });
@@ -215,6 +215,7 @@ export class CustomerGroupListComponent
             )
             .subscribe(
                 () => {
+                    this.refresh();
                     this.notificationService.success(_('common.notify-update-success'), {
                         entity: 'CustomerGroup',
                     });

+ 17 - 17
packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.html

@@ -63,14 +63,12 @@
                             ></vdr-line-fulfillment>
                         </td>
                         <td *ngIf="orderLineCustomFields.length" class="order-line-custom-field align-middle">
-                            <ng-container *ngFor="let customField of orderLineCustomFields">
-                                <vdr-custom-field-control
-                                    [customField]="customField"
-                                    [customFieldsFormGroup]="orderLineCustomFieldsFormArray.get([i])"
-                                    entityName="OrderLine"
-                                    [compact]="true"
-                                ></vdr-custom-field-control>
-                            </ng-container>
+                            <vdr-tabbed-custom-fields
+                                entityName="OrderLine"
+                                [customFields]="orderLineCustomFields"
+                                [customFieldsFormGroup]="orderLineCustomFieldsFormArray.get([i])"
+                                [compact]="true"
+                            ></vdr-tabbed-custom-fields>
                         </td>
                         <td class="align-middle total">
                             {{ line.linePriceWithTax | localeCurrency: order.currencyCode }}
@@ -241,7 +239,8 @@
                             [formControl]="couponCodesControl"
                         >
                             <ng-template ng-option-tmp let-item="item">
-                                <vdr-chip>{{ item.code }}</vdr-chip> {{ item.promotionName }}
+                                <vdr-chip>{{ item.code }}</vdr-chip>
+                                {{ item.promotionName }}
                             </ng-template>
                         </ng-select>
                     </clr-accordion-content>
@@ -257,13 +256,13 @@
                             <vdr-form-field [label]="'order.product-sku' | translate" for="sku"
                                 ><input id="sku" type="text" formControlName="sku"
                             /></vdr-form-field>
-                            <vdr-form-field [label]="'common.price' | translate" for="price"
-                                ><vdr-currency-input
+                            <vdr-form-field [label]="'common.price' | translate" for="price">
+                                <vdr-currency-input
                                     [currencyCode]="order.currencyCode"
                                     id="price"
                                     formControlName="price"
-                                ></vdr-currency-input
-                            ></vdr-form-field>
+                                ></vdr-currency-input>
+                            </vdr-form-field>
                             <vdr-form-field
                                 [label]="
                                     'catalog.price-includes-tax-at'
@@ -276,15 +275,16 @@
                                     clrCheckbox
                                     formControlName="priceIncludesTax"
                             /></vdr-form-field>
-                            <vdr-form-field [label]="'order.tax-rate' | translate" for="taxRate"
-                                ><vdr-affixed-input suffix="%"
+                            <vdr-form-field [label]="'order.tax-rate' | translate" for="taxRate">
+                                <vdr-affixed-input suffix="%"
                                     ><input
                                         id="taxRate"
                                         type="number"
                                         min="0"
                                         max="100"
-                                        formControlName="taxRate" /></vdr-affixed-input
-                            ></vdr-form-field>
+                                        formControlName="taxRate"
+                                /></vdr-affixed-input>
+                            </vdr-form-field>
                             <vdr-form-field [label]="'order.tax-description' | translate" for="taxDescription"
                                 ><input id="taxDescription" type="text" formControlName="taxDescription"
                             /></vdr-form-field>

+ 4 - 0
packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.scss

@@ -6,4 +6,8 @@
     tr.modified td {
         background-color: var(--color-warning-100);
     }
+
+    .order-line-custom-field {
+        text-align: start;
+    }
 }

+ 9 - 1
packages/admin-ui/src/lib/order/src/components/order-editor/order-editor.component.ts

@@ -20,8 +20,10 @@ import {
     ServerConfigService,
     SortOrder,
     SurchargeInput,
+    transformRelationCustomFieldInputs,
 } from '@vendure/admin-ui/core';
 import { assertNever, notNullOrUndefined } from '@vendure/common/lib/shared-utils';
+import { simpleDeepClone } from '@vendure/common/lib/simple-deep-clone';
 import { concat, EMPTY, Observable, of, Subject } from 'rxjs';
 import {
     distinctUntilChanged,
@@ -379,8 +381,14 @@ export class OrderEditorComponent
     }
 
     previewAndModify(order: OrderDetailFragment) {
-        const input: ModifyOrderInput = {
+        const modifyOrderInput: ModifyOrderData = {
             ...this.modifyOrderInput,
+            adjustOrderLines: this.modifyOrderInput.adjustOrderLines.map(line => {
+                return transformRelationCustomFieldInputs(simpleDeepClone(line), this.orderLineCustomFields);
+            }),
+        };
+        const input: ModifyOrderInput = {
+            ...modifyOrderInput,
             ...(this.billingAddressForm.dirty ? { updateBillingAddress: this.billingAddressForm.value } : {}),
             ...(this.shippingAddressForm.dirty
                 ? { updateShippingAddress: this.shippingAddressForm.value }

+ 1 - 0
packages/admin-ui/src/lib/static/styles/_variables.scss

@@ -1,3 +1,4 @@
+@import "./global/sass-overrides";
 @import "~@clr/ui/src/utils/_mixins";
 @import "~@clr/ui/src/utils/_variables.clarity.scss";
 

+ 1 - 1
packages/admin-ui/src/lib/static/styles/global/_sass-overrides.scss

@@ -1,3 +1,3 @@
-// Note: variables defined in this file should use the default! flag so they can be
+// Note: variables defined in this file should use the !default flag so they can be
 // overridden by custom Admin UI applications
 $clr-sidenav-width: 10.8rem !default;

+ 33 - 0
packages/core/e2e/customer-group.e2e-spec.ts

@@ -11,6 +11,7 @@ import { DeletionResult } from './graphql/generated-e2e-shop-types';
 import {
     ADD_CUSTOMERS_TO_GROUP,
     CREATE_CUSTOMER_GROUP,
+    DELETE_CUSTOMER,
     DELETE_CUSTOMER_GROUP,
     GET_CUSTOMER_GROUP,
     GET_CUSTOMER_GROUPS,
@@ -276,4 +277,36 @@ describe('CustomerGroup resolver', () => {
         );
         expect(customerGroups.totalItems).toBe(0);
     });
+
+    // https://github.com/vendure-ecommerce/vendure/issues/1785
+    it('removes customer from group when customer is deleted', async () => {
+        const customer5Id = customers[4].id;
+        const { createCustomerGroup } = await adminClient.query<
+            Codegen.CreateCustomerGroupMutation,
+            Codegen.CreateCustomerGroupMutationVariables
+        >(CREATE_CUSTOMER_GROUP, {
+            input: {
+                name: 'group-1785',
+                customerIds: [customer5Id],
+            },
+        });
+
+        expect(createCustomerGroup.customers.items).toEqual([{ id: customer5Id }]);
+
+        await adminClient.query<Codegen.DeleteCustomerMutation, Codegen.DeleteCustomerMutationVariables>(
+            DELETE_CUSTOMER,
+            {
+                id: customer5Id,
+            },
+        );
+
+        const { customerGroup } = await adminClient.query<
+            Codegen.GetCustomerGroupQuery,
+            Codegen.GetCustomerGroupQueryVariables
+        >(GET_CUSTOMER_GROUP, {
+            id: createCustomerGroup.id,
+        });
+
+        expect(customerGroup?.customers.items).toEqual([]);
+    });
 });

+ 34 - 0
packages/core/e2e/default-search-plugin.e2e-spec.ts

@@ -1682,6 +1682,40 @@ describe('Default search plugin', () => {
                 expect(result.search.items.length).toEqual(2);
             });
         });
+
+        // https://github.com/vendure-ecommerce/vendure/issues/1789
+        describe('input escaping', () => {
+            it('correctly escapes "a & b"', async () => {
+                const result = await adminClient.query<SearchProductsShop.Query, SearchProductShopVariables>(
+                    SEARCH_PRODUCTS,
+                    {
+                        input: {
+                            take: 10,
+                            term: 'laptop & camera',
+                        },
+                    },
+                    {
+                        languageCode: LanguageCode.de,
+                    },
+                );
+                expect(result.search.items).toBeDefined();
+            });
+            it('correctly escapes other special chars', async () => {
+                const result = await adminClient.query<SearchProductsShop.Query, SearchProductShopVariables>(
+                    SEARCH_PRODUCTS,
+                    {
+                        input: {
+                            take: 10,
+                            term: 'a : b ? * (c) ! "foo"',
+                        },
+                    },
+                    {
+                        languageCode: LanguageCode.de,
+                    },
+                );
+                expect(result.search.items).toBeDefined();
+            });
+        });
     });
 });
 

+ 1 - 1
packages/core/src/api/config/generate-permissions.ts

@@ -11,7 +11,7 @@ GraphQL resolvers via the {@link Allow} decorator.
 
 ## Understanding Permission.Owner
 
-\`Permission.Owner\` is a special permission which is used in some of the Vendure resolvers to indicate that that resolver should only
+\`Permission.Owner\` is a special permission which is used in some Vendure resolvers to indicate that that resolver should only
 be accessible to the "owner" of that resource.
 
 For example, the Shop API \`activeCustomer\` query resolver should only return the Customer object for the "owner" of that Customer, i.e.

+ 11 - 0
packages/core/src/api/decorators/allow.decorator.ts

@@ -12,6 +12,17 @@ export const PERMISSIONS_METADATA_KEY = '__permissions__';
  *
  * For REST controllers, it can be applied to route handlers.
  *
+ * ## Allow and Sessions
+ * The `@Allow()` decorator is closely linked to the way Vendure manages sessions. For any operation or route that is decorated
+ * with `@Allow()`, there must be an authenticated session in progress, which would have been created during a prior authentication
+ * step.
+ *
+ * The exception to this is when the operation is decorated with `@Allow(Permission.Owner)`. This is a special permission which is designed
+ * to give access to certain resources to potentially un-authenticated users. For this reason, any operation decorated with this permission
+ * will always have an anonymous session created if no session is currently in progress.
+ *
+ * For more information see [Understanding Permission.Owner](/docs/typescript-api/common/permission/#understanding-permissionowner).
+ *
  * @example
  * ```TypeScript
  *  \@Allow(Permission.SuperAdmin)

+ 13 - 1
packages/core/src/api/resolvers/admin/customer.resolver.ts

@@ -22,6 +22,7 @@ import { PaginatedList } from '@vendure/common/lib/shared-types';
 import { ErrorResultUnion } from '../../../common/error/error-result';
 import { Address } from '../../../entity/address/address.entity';
 import { Customer } from '../../../entity/customer/customer.entity';
+import { CustomerGroupService } from '../../../service/index';
 import { CustomerService } from '../../../service/services/customer.service';
 import { OrderService } from '../../../service/services/order.service';
 import { RequestContext } from '../../common/request-context';
@@ -32,7 +33,11 @@ import { Transaction } from '../../decorators/transaction.decorator';
 
 @Resolver()
 export class CustomerResolver {
-    constructor(private customerService: CustomerService, private orderService: OrderService) {}
+    constructor(
+        private customerService: CustomerService,
+        private customerGroupService: CustomerGroupService,
+        private orderService: OrderService,
+    ) {}
 
     @Query()
     @Allow(Permission.ReadCustomer)
@@ -117,6 +122,13 @@ export class CustomerResolver {
         @Ctx() ctx: RequestContext,
         @Args() args: MutationDeleteCustomerArgs,
     ): Promise<DeletionResponse> {
+        const groups = await this.customerService.getCustomerGroups(ctx, args.id);
+        for (const group of groups) {
+            await this.customerGroupService.removeCustomersFromGroup(ctx, {
+                customerGroupId: group.id,
+                customerIds: [args.id],
+            });
+        }
         return this.customerService.softDelete(ctx, args.id);
     }
 

+ 1 - 0
packages/core/src/api/schema/common/order.type.graphql

@@ -93,6 +93,7 @@ type OrderList implements PaginatedList {
 }
 
 type ShippingLine {
+    id: ID!
     shippingMethod: ShippingMethod!
     price: Int!
     priceWithTax: Int!

+ 3 - 0
packages/core/src/plugin/default-search-plugin/fulltext-search.service.ts

@@ -117,6 +117,7 @@ export class FulltextSearchService {
             switch (this.connection.rawConnection.options.type) {
                 case 'mysql':
                 case 'mariadb':
+                case 'aurora-data-api':
                     this._searchStrategy = new MysqlSearchStrategy();
                     break;
                 case 'sqlite':
@@ -125,6 +126,8 @@ export class FulltextSearchService {
                     this._searchStrategy = new SqliteSearchStrategy();
                     break;
                 case 'postgres':
+                case 'aurora-data-api-pg':
+                case 'cockroachdb':
                     this._searchStrategy = new PostgresSearchStrategy();
                     break;
                 default:

+ 6 - 1
packages/core/src/plugin/default-search-plugin/search-strategy/mysql-search-strategy.ts

@@ -149,6 +149,7 @@ export class MysqlSearchStrategy implements SearchStrategy {
             input;
 
         if (term && term.length > this.minTermLength) {
+            const safeTerm = term.replace(/"/g, '');
             const termScoreQuery = this.connection
                 .getRepository(ctx, SearchIndexItem)
                 .createQueryBuilder('si_inner')
@@ -171,7 +172,11 @@ export class MysqlSearchStrategy implements SearchStrategy {
                     }),
                 )
                 .andWhere('si_inner.channelId = :channelId')
-                .setParameters({ term: `${term}*`, like_term: `%${term}%`, channelId: ctx.channelId });
+                .setParameters({
+                    term: `${safeTerm}*`,
+                    like_term: `%${safeTerm}%`,
+                    channelId: ctx.channelId,
+                });
 
             qb.innerJoin(`(${termScoreQuery.getQuery()})`, 'term_result', 'inner_productId = si.productId')
                 .addSelect(input.groupByProduct ? 'MAX(term_result.score)' : 'term_result.score', 'score')

+ 1 - 1
packages/core/src/plugin/default-search-plugin/search-strategy/postgres-search-strategy.ts

@@ -155,7 +155,7 @@ export class PostgresSearchStrategy implements SearchStrategy {
             ? term
                   .trim()
                   .split(/\s+/g)
-                  .map(t => `${t}:*`)
+                  .map(t => `'${t}':*`)
                   .join(' & ')
             : '';
 

+ 1 - 0
packages/core/src/service/services/customer-group.service.ts

@@ -77,6 +77,7 @@ export class CustomerGroupService {
             .leftJoin('customer.groups', 'group')
             .leftJoin('customer.channels', 'channel')
             .andWhere('group.id = :groupId', { groupId: customerGroupId })
+            .andWhere('customer.deletedAt IS NULL', { groupId: customerGroupId })
             .andWhere('channel.id =:channelId', { channelId: ctx.channelId })
             .getManyAndCount()
             .then(([items, totalItems]) => ({ items, totalItems }));

+ 4 - 0
packages/create/templates/vendure-config.hbs

@@ -73,6 +73,10 @@ export const config: VendureConfig = {
         AssetServerPlugin.init({
             route: 'assets',
             assetUploadDir: path.join(__dirname, '../static/assets'),
+            // For local dev, the correct value for assetUrlPrefix should
+            // be guessed correctly, but for production it will usually need
+            // to be set manually to match your production url.
+            assetUrlPrefix: IS_DEV ? undefined : 'https://www.my-shop.com/assets',
         }),
         DefaultJobQueuePlugin.init({ useDatabaseForBuffer: true }),
         DefaultSearchPlugin.init({ bufferUpdates: false, indexStockStatus: true }),