Przeglądaj źródła

Merge branch 'master' into next

Michael Bromley 4 lat temu
rodzic
commit
8bf536dbf2

+ 23 - 0
CHANGELOG.md

@@ -1,3 +1,26 @@
+## <small>0.18.4 (2021-02-10)</small>
+
+
+#### Features
+
+* **core** Implement productVariants list query in Admin API ([6d830a0](https://github.com/vendure-ecommerce/vendure/commit/6d830a0))
+* **core** Language fallback when querying Product/Collection by slug ([5967c8a](https://github.com/vendure-ecommerce/vendure/commit/5967c8a)), closes [#538](https://github.com/vendure-ecommerce/vendure/issues/538)
+
+#### Fixes
+
+* **admin-ui** Fix modification of order addresses ([cd9a812](https://github.com/vendure-ecommerce/vendure/commit/cd9a812)), closes [#688](https://github.com/vendure-ecommerce/vendure/issues/688)
+* **common** Correctly normalize strings with single quotes ([d12f369](https://github.com/vendure-ecommerce/vendure/commit/d12f369)), closes [#679](https://github.com/vendure-ecommerce/vendure/issues/679)
+* **core** Clean up Sessions when deleting a Channel ([7e7d4b8](https://github.com/vendure-ecommerce/vendure/commit/7e7d4b8)), closes [#686](https://github.com/vendure-ecommerce/vendure/issues/686)
+* **core** Correctly constrain inventory on addItemToOrder mutation ([8975247](https://github.com/vendure-ecommerce/vendure/commit/8975247)), closes [#691](https://github.com/vendure-ecommerce/vendure/issues/691)
+* **core** Do not modify billing address when shipping address changed ([7e9a709](https://github.com/vendure-ecommerce/vendure/commit/7e9a709)), closes [#688](https://github.com/vendure-ecommerce/vendure/issues/688)
+* **core** Fix bad column reference in querybuilder string ([61e9f83](https://github.com/vendure-ecommerce/vendure/commit/61e9f83)), closes [#687](https://github.com/vendure-ecommerce/vendure/issues/687)
+* **core** Fix sorting by price on productVariants list ([0102232](https://github.com/vendure-ecommerce/vendure/commit/0102232)), closes [#690](https://github.com/vendure-ecommerce/vendure/issues/690)
+* **core** Fix sorting by translatable fields in list queries ([d00bafb](https://github.com/vendure-ecommerce/vendure/commit/d00bafb)), closes [#689](https://github.com/vendure-ecommerce/vendure/issues/689)
+* **core** Loosen restriction on transitioning to PaymentAuthorized ([59d39d6](https://github.com/vendure-ecommerce/vendure/commit/59d39d6))
+* **core** Products without variants are indexed by DefaultSearchPlugin ([2dab174](https://github.com/vendure-ecommerce/vendure/commit/2dab174)), closes [#609](https://github.com/vendure-ecommerce/vendure/issues/609)
+* **core** Reduce chance of index err in assigning variants to channels ([58e3f7b](https://github.com/vendure-ecommerce/vendure/commit/58e3f7b))
+* **elasticsearch-plugin** Products without variants are indexed ([21b6aa3](https://github.com/vendure-ecommerce/vendure/commit/21b6aa3)), closes [#609](https://github.com/vendure-ecommerce/vendure/issues/609)
+
 ## <small>0.18.3 (2021-01-29)</small>
 
 

+ 3 - 0
docs/content/docs/plugins/writing-a-vendure-plugin.md

@@ -38,6 +38,9 @@ import { VendurePlugin } from '@vendure/core';
 export class RandomCatPlugin {}
 ```
 
+Changing the database schema by `CustomFields` configuration requires a synchronized database. See [Customizing Models With Custom Fields]({{< relref "customizing-models" >}}) for more details.
+
+
 ### Step 2: Create a service to fetch the data
 
 Now we will create a service which is responsible for making the HTTP call to the random.cat API and returning the URL of a random cat image:

+ 1 - 1
lerna.json

@@ -2,7 +2,7 @@
   "packages": [
     "packages/*"
   ],
-  "version": "0.18.3",
+  "version": "0.18.4",
   "npmClient": "yarn",
   "useWorkspaces": true,
   "command": {

+ 3 - 3
packages/admin-ui-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/admin-ui-plugin",
-  "version": "0.18.3",
+  "version": "0.18.4",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
   "files": [
@@ -19,8 +19,8 @@
   "devDependencies": {
     "@types/express": "^4.17.8",
     "@types/fs-extra": "^9.0.1",
-    "@vendure/common": "^0.18.3",
-    "@vendure/core": "^0.18.3",
+    "@vendure/common": "^0.18.4",
+    "@vendure/core": "^0.18.4",
     "express": "^4.17.1",
     "rimraf": "^3.0.2",
     "typescript": "4.0.3"

+ 2 - 2
packages/admin-ui/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/admin-ui",
-  "version": "0.18.3",
+  "version": "0.18.4",
   "license": "MIT",
   "scripts": {
     "ng": "ng",
@@ -36,7 +36,7 @@
     "@ng-select/ng-select": "^5.0.3",
     "@ngx-translate/core": "^13.0.0",
     "@ngx-translate/http-loader": "^6.0.0",
-    "@vendure/common": "^0.18.3",
+    "@vendure/common": "^0.18.4",
     "@webcomponents/custom-elements": "^1.2.4",
     "apollo-angular": "^2.0.4",
     "apollo-upload-client": "^12.1.0",

+ 1 - 1
packages/admin-ui/src/lib/core/src/common/version.ts

@@ -1,2 +1,2 @@
 // Auto-generated by the set-version.js script.
-export const ADMIN_UI_VERSION = '0.18.3';
+export const ADMIN_UI_VERSION = '0.18.4';

+ 3 - 3
packages/asset-server-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/asset-server-plugin",
-  "version": "0.18.3",
+  "version": "0.18.4",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
   "files": [
@@ -22,8 +22,8 @@
     "@types/fs-extra": "^9.0.1",
     "@types/node-fetch": "^2.5.7",
     "@types/sharp": "^0.26.0",
-    "@vendure/common": "^0.18.3",
-    "@vendure/core": "^0.18.3",
+    "@vendure/common": "^0.18.4",
+    "@vendure/core": "^0.18.4",
     "aws-sdk": "^2.766.0",
     "express": "^4.17.1",
     "node-fetch": "^2.6.1",

+ 1 - 1
packages/common/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/common",
-  "version": "0.18.3",
+  "version": "0.18.4",
   "main": "index.js",
   "license": "MIT",
   "scripts": {

+ 60 - 0
packages/core/e2e/list-query-builder.e2e-spec.ts

@@ -633,6 +633,66 @@ describe('ListQueryBuilder', () => {
             expect(testEntities.items.map((x: any) => x.label)).toEqual(['B', 'A', 'E', 'D', 'C']);
         });
     });
+
+    describe('multiple clauses', () => {
+        it('sort by translated field en & filter', async () => {
+            const { testEntities } = await adminClient.query(GET_LIST, {
+                options: {
+                    sort: {
+                        name: SortOrder.ASC,
+                    },
+                    filter: {
+                        order: {
+                            gte: 1,
+                        },
+                    },
+                },
+            });
+            expect(testEntities.items.map((x: any) => x.name)).toEqual(['bike', 'cake', 'dog', 'egg']);
+        });
+
+        it('sort by translated field de & filter', async () => {
+            const { testEntities } = await adminClient.query(
+                GET_LIST,
+                {
+                    options: {
+                        sort: {
+                            name: SortOrder.ASC,
+                        },
+                        filter: {
+                            order: {
+                                gte: 1,
+                            },
+                        },
+                    },
+                },
+                { languageCode: LanguageCode.de },
+            );
+            expect(testEntities.items.map((x: any) => x.name)).toEqual(['egg', 'fahrrad', 'hund', 'kuchen']);
+        });
+
+        it('sort by translated field de & filter & pagination', async () => {
+            const { testEntities } = await adminClient.query(
+                GET_LIST,
+                {
+                    options: {
+                        sort: {
+                            name: SortOrder.ASC,
+                        },
+                        filter: {
+                            order: {
+                                gte: 1,
+                            },
+                        },
+                        take: 2,
+                        skip: 1,
+                    },
+                },
+                { languageCode: LanguageCode.de },
+            );
+            expect(testEntities.items.map((x: any) => x.name)).toEqual(['fahrrad', 'hund']);
+        });
+    });
 });
 
 const GET_LIST = gql`

+ 2 - 2
packages/core/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/core",
-  "version": "0.18.3",
+  "version": "0.18.4",
   "description": "A modern, headless ecommerce framework",
   "repository": {
     "type": "git",
@@ -48,7 +48,7 @@
     "@nestjs/testing": "7.4.4",
     "@nestjs/typeorm": "7.1.5",
     "@types/fs-extra": "^9.0.1",
-    "@vendure/common": "^0.18.3",
+    "@vendure/common": "^0.18.4",
     "apollo-server-express": "2.18.1",
     "bcrypt": "^5.0.0",
     "body-parser": "^1.19.0",

+ 31 - 27
packages/core/src/service/helpers/list-query-builder/list-query-builder.ts

@@ -154,34 +154,38 @@ export class ListQueryBuilder implements OnApplicationBootstrap {
                 'translations',
             );
 
-            qb.andWhere(`${translationsAlias}.languageCode = :languageCode`, { languageCode });
-
-            if (languageCode !== this.configService.defaultLanguageCode) {
-                // If the current languageCode is not the default, then we create a more
-                // complex WHERE clause to allow us to use the non-default translations and
-                // fall back to the default language if no translation exists.
-                qb.orWhere(
-                    new Brackets(qb1 => {
-                        const translationEntity = translationColumns[0].entityMetadata.target;
-                        const subQb1 = this.connection.rawConnection
-                            .createQueryBuilder(translationEntity, 'translation')
-                            .where(`translation.base = ${alias}.id`)
-                            .andWhere('translation.languageCode = :defaultLanguageCode');
-                        const subQb2 = this.connection.rawConnection
-                            .createQueryBuilder(translationEntity, 'translation')
-                            .where(`translation.base = ${alias}.id`)
-                            .andWhere('translation.languageCode = :nonDefaultLanguageCode');
-
-                        qb1.where(`EXISTS (${subQb1.getQuery()})`).andWhere(
-                            `NOT EXISTS (${subQb2.getQuery()})`,
+            qb.andWhere(
+                new Brackets(qb1 => {
+                    qb1.where(`${translationsAlias}.languageCode = :languageCode`, { languageCode });
+
+                    if (languageCode !== this.configService.defaultLanguageCode) {
+                        // If the current languageCode is not the default, then we create a more
+                        // complex WHERE clause to allow us to use the non-default translations and
+                        // fall back to the default language if no translation exists.
+                        qb1.orWhere(
+                            new Brackets(qb2 => {
+                                const translationEntity = translationColumns[0].entityMetadata.target;
+                                const subQb1 = this.connection.rawConnection
+                                    .createQueryBuilder(translationEntity, 'translation')
+                                    .where(`translation.base = ${alias}.id`)
+                                    .andWhere('translation.languageCode = :defaultLanguageCode');
+                                const subQb2 = this.connection.rawConnection
+                                    .createQueryBuilder(translationEntity, 'translation')
+                                    .where(`translation.base = ${alias}.id`)
+                                    .andWhere('translation.languageCode = :nonDefaultLanguageCode');
+
+                                qb2.where(`EXISTS (${subQb1.getQuery()})`).andWhere(
+                                    `NOT EXISTS (${subQb2.getQuery()})`,
+                                );
+                            }),
                         );
-                    }),
-                );
-                qb.setParameters({
-                    nonDefaultLanguageCode: languageCode,
-                    defaultLanguageCode: this.configService.defaultLanguageCode,
-                });
-            }
+                        qb.setParameters({
+                            nonDefaultLanguageCode: languageCode,
+                            defaultLanguageCode: this.configService.defaultLanguageCode,
+                        });
+                    }
+                }),
+            );
         }
     }
 

+ 3 - 3
packages/create/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/create",
-  "version": "0.18.3",
+  "version": "0.18.4",
   "license": "MIT",
   "bin": {
     "create": "./index.js"
@@ -26,13 +26,13 @@
     "@types/handlebars": "^4.1.0",
     "@types/listr": "^0.14.2",
     "@types/semver": "^6.2.2",
-    "@vendure/core": "^0.18.3",
+    "@vendure/core": "^0.18.4",
     "rimraf": "^3.0.2",
     "ts-node": "^9.0.0",
     "typescript": "4.0.3"
   },
   "dependencies": {
-    "@vendure/common": "^0.18.3",
+    "@vendure/common": "^0.18.4",
     "chalk": "^4.1.0",
     "commander": "^6.1.0",
     "cross-spawn": "^7.0.3",

+ 9 - 9
packages/dev-server/package.json

@@ -1,6 +1,6 @@
 {
   "name": "dev-server",
-  "version": "0.18.3",
+  "version": "0.18.4",
   "main": "index.js",
   "license": "MIT",
   "private": true,
@@ -14,18 +14,18 @@
     "load-test:100k": "node -r ts-node/register load-testing/run-load-test.ts 100000"
   },
   "dependencies": {
-    "@vendure/admin-ui-plugin": "^0.18.3",
-    "@vendure/asset-server-plugin": "^0.18.3",
-    "@vendure/common": "^0.18.3",
-    "@vendure/core": "^0.18.3",
-    "@vendure/elasticsearch-plugin": "^0.18.3",
-    "@vendure/email-plugin": "^0.18.3",
+    "@vendure/admin-ui-plugin": "^0.18.4",
+    "@vendure/asset-server-plugin": "^0.18.4",
+    "@vendure/common": "^0.18.4",
+    "@vendure/core": "^0.18.4",
+    "@vendure/elasticsearch-plugin": "^0.18.4",
+    "@vendure/email-plugin": "^0.18.4",
     "typescript": "4.0.3"
   },
   "devDependencies": {
     "@types/csv-stringify": "^3.1.0",
-    "@vendure/testing": "^0.18.3",
-    "@vendure/ui-devkit": "^0.18.3",
+    "@vendure/testing": "^0.18.4",
+    "@vendure/ui-devkit": "^0.18.4",
     "concurrently": "^5.0.0",
     "csv-stringify": "^5.3.3"
   }

+ 3 - 3
packages/elasticsearch-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/elasticsearch-plugin",
-  "version": "0.18.3",
+  "version": "0.18.4",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -22,8 +22,8 @@
     "deepmerge": "^4.2.2"
   },
   "devDependencies": {
-    "@vendure/common": "^0.18.3",
-    "@vendure/core": "^0.18.3",
+    "@vendure/common": "^0.18.4",
+    "@vendure/core": "^0.18.4",
     "rimraf": "^3.0.2",
     "typescript": "4.0.3"
   }

+ 3 - 3
packages/email-plugin/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/email-plugin",
-  "version": "0.18.3",
+  "version": "0.18.4",
   "license": "MIT",
   "main": "lib/index.js",
   "types": "lib/index.d.ts",
@@ -33,8 +33,8 @@
     "@types/handlebars": "^4.1.0",
     "@types/mjml": "^4.0.4",
     "@types/nodemailer": "^6.4.0",
-    "@vendure/common": "^0.18.3",
-    "@vendure/core": "^0.18.3",
+    "@vendure/common": "^0.18.4",
+    "@vendure/core": "^0.18.4",
     "rimraf": "^3.0.2",
     "typescript": "4.0.3"
   }

+ 13 - 5
packages/email-plugin/src/email-processor.ts

@@ -4,10 +4,16 @@ import fs from 'fs-extra';
 import { deserializeAttachments } from './attachment-utils';
 import { isDevModeOptions } from './common';
 import { loggerCtx } from './constants';
-import { EmailSender } from './email-sender';
+import { DefaultEmailSender } from './email-sender';
 import { HandlebarsMjmlGenerator } from './handlebars-mjml-generator';
 import { TemplateLoader } from './template-loader';
-import { EmailPluginOptions, EmailTransportOptions, IntermediateEmailDetails } from './types';
+import {
+    EmailGenerator,
+    EmailPluginOptions,
+    EmailSender,
+    EmailTransportOptions,
+    IntermediateEmailDetails,
+} from './types';
 
 /**
  * This class combines the template loading, generation, and email sending - the actual "work" of
@@ -17,15 +23,17 @@ import { EmailPluginOptions, EmailTransportOptions, IntermediateEmailDetails } f
 export class EmailProcessor {
     protected templateLoader: TemplateLoader;
     protected emailSender: EmailSender;
-    protected generator: HandlebarsMjmlGenerator;
+    protected generator: EmailGenerator;
     protected transport: EmailTransportOptions;
 
     constructor(protected options: EmailPluginOptions) {}
 
     async init() {
         this.templateLoader = new TemplateLoader(this.options.templatePath);
-        this.emailSender = new EmailSender();
-        this.generator = new HandlebarsMjmlGenerator();
+        this.emailSender = this.options.emailSender ? this.options.emailSender : new DefaultEmailSender();
+        this.generator = this.options.emailGenerator
+            ? this.options.emailGenerator
+            : new HandlebarsMjmlGenerator();
         if (this.generator.onInit) {
             await this.generator.onInit.call(this.generator, this.options);
         }

+ 8 - 2
packages/email-plugin/src/email-sender.ts

@@ -12,7 +12,13 @@ import { Stream } from 'stream';
 import { format } from 'util';
 
 import { loggerCtx } from './constants';
-import { EmailDetails, EmailTransportOptions, SendmailTransportOptions, SMTPTransportOptions } from './types';
+import {
+    EmailDetails,
+    EmailSender,
+    EmailTransportOptions,
+    SendmailTransportOptions,
+    SMTPTransportOptions,
+} from './types';
 
 export type StreamTransportInfo = {
     envelope: {
@@ -26,7 +32,7 @@ export type StreamTransportInfo = {
 /**
  * Uses the configured transport to send the generated email.
  */
-export class EmailSender {
+export class DefaultEmailSender implements EmailSender {
     private _smtpTransport: Mail | undefined;
     private _sendMailTransport: Mail | undefined;
 

+ 35 - 1
packages/email-plugin/src/plugin.spec.ts

@@ -20,7 +20,7 @@ import { orderConfirmationHandler } from './default-email-handlers';
 import { EmailEventHandler } from './event-handler';
 import { EmailEventListener } from './event-listener';
 import { EmailPlugin } from './plugin';
-import { EmailPluginOptions } from './types';
+import { EmailDetails, EmailPluginOptions, EmailSender, EmailTransportOptions } from './types';
 
 describe('EmailPlugin', () => {
     let eventBus: EventBus;
@@ -624,8 +624,42 @@ describe('EmailPlugin', () => {
             expect(testingLogger.errorSpy.mock.calls[0][0]).toContain(`something went horribly wrong!`);
         });
     });
+
+    describe('custom sender', () => {
+        it('should allow a custom sender to be utilized', async () => {
+            const ctx = RequestContext.deserialize({
+                _channel: { code: DEFAULT_CHANNEL_CODE },
+                _languageCode: LanguageCode.en,
+            } as any);
+            const handler = new EmailEventListener('test')
+                .on(MockEvent)
+                .setFrom('"test from" <noreply@test.com>')
+                .setRecipient(() => 'test@test.com')
+                .setSubject('Hello')
+                .setTemplateVars(event => ({ subjectVar: 'foo' }));
+
+            const fakeSender = new FakeCustomSender();
+            const send = jest.fn();
+            fakeSender.send = send;
+
+            await initPluginWithHandlers([handler], {
+                emailSender: fakeSender,
+            });
+
+            eventBus.publish(new MockEvent(ctx, true));
+            await pause();
+            expect(send.mock.calls[0][0].subject).toBe('Hello');
+            expect(send.mock.calls[0][0].recipient).toBe('test@test.com');
+            expect(send.mock.calls[0][0].from).toBe('"test from" <noreply@test.com>');
+            expect(onSend).toHaveBeenCalledTimes(0);
+        });
+    });
 });
 
+class FakeCustomSender implements EmailSender {
+    send: (email: EmailDetails<'unserialized'>, options: EmailTransportOptions) => void;
+}
+
 const pause = () => new Promise(resolve => setTimeout(resolve, 100));
 
 class MockEvent extends VendureEvent {

+ 16 - 0
packages/email-plugin/src/types.ts

@@ -59,6 +59,18 @@ export interface EmailPluginOptions {
      * email.
      */
     globalTemplateVars?: { [key: string]: any };
+    /**
+     * @description
+     * An optional allowed EmailSender, used to allow custom implementations of the send functionality
+     * while still utilizing the existing emailPlugin functionality.
+     */
+    emailSender?: EmailSender;
+    /**
+     * @description
+     * An optional allowed EmailGenerator, used to allow custom email generation functionality to
+     * better match with custom email sending functionality.
+     */
+    emailGenerator?: EmailGenerator;
 }
 
 /**
@@ -262,6 +274,10 @@ export interface TestingTransportOptions {
     onSend: (details: EmailDetails) => void;
 }
 
+export interface EmailSender {
+    send: (email: EmailDetails, options: EmailTransportOptions) => void;
+}
+
 /**
  * @description
  * An EmailGenerator generates the subject and body details of an email.

+ 3 - 3
packages/testing/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/testing",
-  "version": "0.18.3",
+  "version": "0.18.4",
   "description": "End-to-end testing tools for Vendure projects",
   "keywords": [
     "vendure",
@@ -33,7 +33,7 @@
   },
   "dependencies": {
     "@types/node-fetch": "^2.5.4",
-    "@vendure/common": "^0.18.3",
+    "@vendure/common": "^0.18.4",
     "faker": "^4.1.0",
     "form-data": "^3.0.0",
     "graphql": "15.3.0",
@@ -44,7 +44,7 @@
   "devDependencies": {
     "@types/mysql": "^2.15.15",
     "@types/pg": "^7.14.5",
-    "@vendure/core": "^0.18.3",
+    "@vendure/core": "^0.18.4",
     "mysql": "^2.18.1",
     "pg": "^8.4.0",
     "rimraf": "^3.0.0",

+ 4 - 4
packages/ui-devkit/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@vendure/ui-devkit",
-  "version": "0.18.3",
+  "version": "0.18.4",
   "description": "A library for authoring Vendure Admin UI extensions",
   "keywords": [
     "vendure",
@@ -39,8 +39,8 @@
     "@angular/cli": "^10.1.4",
     "@angular/compiler": "^10.1.4",
     "@angular/compiler-cli": "^10.1.4",
-    "@vendure/admin-ui": "^0.18.3",
-    "@vendure/common": "^0.18.3",
+    "@vendure/admin-ui": "^0.18.4",
+    "@vendure/common": "^0.18.4",
     "chalk": "^4.1.0",
     "chokidar": "^3.4.2",
     "fs-extra": "^9.0.1",
@@ -51,7 +51,7 @@
     "@rollup/plugin-node-resolve": "^9.0.0",
     "@types/fs-extra": "^9.0.1",
     "@types/glob": "^7.1.3",
-    "@vendure/core": "^0.18.3",
+    "@vendure/core": "^0.18.4",
     "rimraf": "^3.0.2",
     "rollup": "^2.28.2",
     "rollup-plugin-terser": "^7.0.2",