Browse Source

refactor(server): Create ListQueryBuilder helper

Michael Bromley 7 years ago
parent
commit
5aaf025ea2

+ 0 - 55
server/src/service/helpers/build-list-query.ts

@@ -1,55 +0,0 @@
-import { ID, Type } from 'shared/shared-types';
-import { Connection, FindManyOptions, SelectQueryBuilder } from 'typeorm';
-import { FindOptionsUtils } from 'typeorm/find-options/FindOptionsUtils';
-
-import { ListQueryOptions } from '../../common/types/common-types';
-import { VendureEntity } from '../../entity/base/base.entity';
-
-import { parseChannelParam } from './parse-channel-param';
-import { parseFilterParams } from './parse-filter-params';
-import { parseSortParams } from './parse-sort-params';
-
-/**
- * Creates and configures a SelectQueryBuilder for queries that return paginated lists of entities.
- */
-export function buildListQuery<T extends VendureEntity>(
-    connection: Connection,
-    entity: Type<T>,
-    options: ListQueryOptions<T> = {},
-    relations?: string[],
-    channelId?: ID,
-): SelectQueryBuilder<T> {
-    const skip = options.skip;
-    let take = options.take;
-    if (options.skip !== undefined && options.take === undefined) {
-        take = Number.MAX_SAFE_INTEGER;
-    }
-    const sort = parseSortParams(connection, entity, options.sort);
-    const filter = parseFilterParams(connection, entity, options.filter);
-
-    const qb = connection.createQueryBuilder<T>(entity, entity.name.toLowerCase());
-    FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
-        relations,
-        take,
-        skip,
-    } as FindManyOptions<T>);
-    // tslint:disable-next-line:no-non-null-assertion
-    FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);
-
-    filter.forEach(({ clause, parameters }, index) => {
-        if (index === 0) {
-            qb.where(clause, parameters);
-        } else {
-            qb.andWhere(clause, parameters);
-        }
-    });
-
-    if (channelId) {
-        const channelFilter = parseChannelParam(connection, entity, channelId);
-        if (channelFilter) {
-            qb.andWhere(channelFilter.clause, channelFilter.parameters);
-        }
-    }
-
-    return qb.orderBy(sort);
-}

+ 61 - 0
server/src/service/helpers/list-query-builder/list-query-builder.ts

@@ -0,0 +1,61 @@
+import { Injectable } from '@nestjs/common';
+import { InjectConnection } from '@nestjs/typeorm';
+import { ID, Type } from 'shared/shared-types';
+import { Connection, FindManyOptions, SelectQueryBuilder } from 'typeorm';
+import { FindOptionsUtils } from 'typeorm/find-options/FindOptionsUtils';
+
+import { ListQueryOptions } from '../../../common/types/common-types';
+import { VendureEntity } from '../../../entity/base/base.entity';
+
+import { parseChannelParam } from './parse-channel-param';
+import { parseFilterParams } from './parse-filter-params';
+import { parseSortParams } from './parse-sort-params';
+
+@Injectable()
+export class ListQueryBuilder {
+    constructor(@InjectConnection() private connection: Connection) {}
+
+    /**
+     * Creates and configures a SelectQueryBuilder for queries that return paginated lists of entities.
+     */
+    build<T extends VendureEntity>(
+        entity: Type<T>,
+        options: ListQueryOptions<T> = {},
+        relations?: string[],
+        channelId?: ID,
+    ): SelectQueryBuilder<T> {
+        const skip = options.skip;
+        let take = options.take;
+        if (options.skip !== undefined && options.take === undefined) {
+            take = Number.MAX_SAFE_INTEGER;
+        }
+        const sort = parseSortParams(this.connection, entity, options.sort);
+        const filter = parseFilterParams(this.connection, entity, options.filter);
+
+        const qb = this.connection.createQueryBuilder<T>(entity, entity.name.toLowerCase());
+        FindOptionsUtils.applyFindManyOptionsOrConditionsToQueryBuilder(qb, {
+            relations,
+            take,
+            skip,
+        } as FindManyOptions<T>);
+        // tslint:disable-next-line:no-non-null-assertion
+        FindOptionsUtils.joinEagerRelations(qb, qb.alias, qb.expressionMap.mainAlias!.metadata);
+
+        filter.forEach(({ clause, parameters }, index) => {
+            if (index === 0) {
+                qb.where(clause, parameters);
+            } else {
+                qb.andWhere(clause, parameters);
+            }
+        });
+
+        if (channelId) {
+            const channelFilter = parseChannelParam(this.connection, entity, channelId);
+            if (channelFilter) {
+                qb.andWhere(channelFilter.clause, channelFilter.parameters);
+            }
+        }
+
+        return qb.orderBy(sort);
+    }
+}

+ 3 - 3
server/src/service/helpers/parse-channel-param.spec.ts → server/src/service/helpers/list-query-builder/parse-channel-param.spec.ts

@@ -1,6 +1,6 @@
-import { Channel } from '../../entity/channel/channel.entity';
-import { Customer } from '../../entity/customer/customer.entity';
-import { Product } from '../../entity/product/product.entity';
+import { Channel } from '../../../entity/channel/channel.entity';
+import { Customer } from '../../../entity/customer/customer.entity';
+import { Product } from '../../../entity/product/product.entity';
 
 import { parseChannelParam } from './parse-channel-param';
 import { MockConnection } from './parse-sort-params.spec';

+ 1 - 1
server/src/service/helpers/parse-channel-param.ts → server/src/service/helpers/list-query-builder/parse-channel-param.ts

@@ -1,7 +1,7 @@
 import { ID, Type } from 'shared/shared-types';
 import { Connection } from 'typeorm';
 
-import { VendureEntity } from '../../entity/base/base.entity';
+import { VendureEntity } from '../../../entity/base/base.entity';
 
 import { WhereCondition } from './parse-filter-params';
 

+ 3 - 3
server/src/service/helpers/parse-filter-params.spec.ts → server/src/service/helpers/list-query-builder/parse-filter-params.spec.ts

@@ -1,6 +1,6 @@
-import { FilterParameter } from '../../common/types/common-types';
-import { ProductTranslation } from '../../entity/product/product-translation.entity';
-import { Product } from '../../entity/product/product.entity';
+import { FilterParameter } from '../../../common/types/common-types';
+import { ProductTranslation } from '../../../entity/product/product-translation.entity';
+import { Product } from '../../../entity/product/product.entity';
 
 import { parseFilterParams } from './parse-filter-params';
 import { MockConnection } from './parse-sort-params.spec';

+ 3 - 3
server/src/service/helpers/parse-filter-params.ts → server/src/service/helpers/list-query-builder/parse-filter-params.ts

@@ -3,8 +3,8 @@ import { assertNever } from 'shared/shared-utils';
 import { Connection } from 'typeorm';
 import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
 
-import { VendureEntity } from '../../entity/base/base.entity';
-import { I18nError } from '../../i18n/i18n-error';
+import { VendureEntity } from '../../../entity/base/base.entity';
+import { I18nError } from '../../../i18n/i18n-error';
 
 import {
     BooleanOperators,
@@ -13,7 +13,7 @@ import {
     NullOptionals,
     NumberOperators,
     StringOperators,
-} from '../../common/types/common-types';
+} from '../../../common/types/common-types';
 
 export interface WhereCondition {
     clause: string;

+ 4 - 4
server/src/service/helpers/parse-sort-params.spec.ts → server/src/service/helpers/list-query-builder/parse-sort-params.spec.ts

@@ -2,10 +2,10 @@ import { Type } from 'shared/shared-types';
 import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
 import { RelationMetadata } from 'typeorm/metadata/RelationMetadata';
 
-import { SortParameter } from '../../common/types/common-types';
-import { ProductTranslation } from '../../entity/product/product-translation.entity';
-import { Product } from '../../entity/product/product.entity';
-import { I18nError } from '../../i18n/i18n-error';
+import { SortParameter } from '../../../common/types/common-types';
+import { ProductTranslation } from '../../../entity/product/product-translation.entity';
+import { Product } from '../../../entity/product/product.entity';
+import { I18nError } from '../../../i18n/i18n-error';
 
 import { parseSortParams } from './parse-sort-params';
 

+ 3 - 3
server/src/service/helpers/parse-sort-params.ts → server/src/service/helpers/list-query-builder/parse-sort-params.ts

@@ -2,10 +2,10 @@ import { Type } from 'shared/shared-types';
 import { Connection, OrderByCondition } from 'typeorm';
 import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
 
-import { VendureEntity } from '../../entity/base/base.entity';
-import { I18nError } from '../../i18n/i18n-error';
+import { VendureEntity } from '../../../entity/base/base.entity';
+import { I18nError } from '../../../i18n/i18n-error';
 
-import { NullOptionals, SortParameter } from '../../common/types/common-types';
+import { NullOptionals, SortParameter } from '../../../common/types/common-types';
 
 /**
  * Parses the provided SortParameter array against the metadata of the given entity, ensuring that only

+ 7 - 1
server/src/service/helpers/tax-calculator/tax-calculator.spec.ts

@@ -8,6 +8,7 @@ import { TaxCategory } from '../../../entity/tax-category/tax-category.entity';
 import { TaxRate } from '../../../entity/tax-rate/tax-rate.entity';
 import { Zone } from '../../../entity/zone/zone.entity';
 import { TaxRateService } from '../../services/tax-rate.service';
+import { ListQueryBuilder } from '../list-query-builder/list-query-builder';
 
 import { TaxCalculator } from './tax-calculator';
 
@@ -99,7 +100,12 @@ describe('TaxCalculator', () => {
 
     beforeEach(async () => {
         const module = await Test.createTestingModule({
-            providers: [TaxCalculator, TaxRateService, { provide: Connection, useClass: MockConnection }],
+            providers: [
+                TaxCalculator,
+                TaxRateService,
+                { provide: Connection, useClass: MockConnection },
+                { provide: ListQueryBuilder, useValue: {} },
+            ],
         }).compile();
 
         taxCalculator = module.get(TaxCalculator);

+ 2 - 0
server/src/service/service.module.ts

@@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
 import { ConfigModule } from '../config/config.module';
 import { getConfig } from '../config/vendure-config';
 
+import { ListQueryBuilder } from './helpers/list-query-builder/list-query-builder';
 import { OrderCalculator } from './helpers/order-calculator/order-calculator';
 import { PasswordCiper } from './helpers/password-cipher/password-ciper';
 import { TaxCalculator } from './helpers/tax-calculator/tax-calculator';
@@ -65,6 +66,7 @@ const exportedProviders = [
         TranslationUpdaterService,
         TaxCalculator,
         OrderCalculator,
+        ListQueryBuilder,
     ],
     exports: exportedProviders,
 })

+ 4 - 2
server/src/service/services/administrator.service.ts

@@ -9,7 +9,7 @@ import { ListQueryOptions } from '../../common/types/common-types';
 import { Administrator } from '../../entity/administrator/administrator.entity';
 import { User } from '../../entity/user/user.entity';
 import { I18nError } from '../../i18n/i18n-error';
-import { buildListQuery } from '../helpers/build-list-query';
+import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { PasswordCiper } from '../helpers/password-cipher/password-ciper';
 import { patchEntity } from '../helpers/patch-entity';
 
@@ -19,6 +19,7 @@ import { RoleService } from './role.service';
 export class AdministratorService {
     constructor(
         @InjectConnection() private connection: Connection,
+        private listQueryBuilder: ListQueryBuilder,
         private passwordCipher: PasswordCiper,
         private roleService: RoleService,
     ) {}
@@ -28,7 +29,8 @@ export class AdministratorService {
     }
 
     findAll(options?: ListQueryOptions<Administrator>): Promise<PaginatedList<Administrator>> {
-        return buildListQuery(this.connection, Administrator, options, ['user', 'user.roles'])
+        return this.listQueryBuilder
+            .build(Administrator, options, ['user', 'user.roles'])
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,

+ 8 - 4
server/src/service/services/asset.service.ts

@@ -8,12 +8,15 @@ import { ListQueryOptions } from '../../common/types/common-types';
 import { getAssetType } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
 import { Asset } from '../../entity/asset/asset.entity';
-
-import { buildListQuery } from '../helpers/build-list-query';
+import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 
 @Injectable()
 export class AssetService {
-    constructor(@InjectConnection() private connection: Connection, private configService: ConfigService) {}
+    constructor(
+        @InjectConnection() private connection: Connection,
+        private configService: ConfigService,
+        private listQueryBuilder: ListQueryBuilder,
+    ) {}
 
     findOne(id: ID): Promise<Asset | undefined> {
         return this.connection.getRepository(Asset).findOne(id);
@@ -24,7 +27,8 @@ export class AssetService {
     }
 
     findAll(options?: ListQueryOptions<Asset>): Promise<PaginatedList<Asset>> {
-        return buildListQuery(this.connection, Asset, options)
+        return this.listQueryBuilder
+            .build(Asset, options)
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,

+ 7 - 3
server/src/service/services/country.service.ts

@@ -8,15 +8,19 @@ import { ListQueryOptions } from '../../common/types/common-types';
 import { assertFound } from '../../common/utils';
 import { Country } from '../../entity/country/country.entity';
 import { I18nError } from '../../i18n/i18n-error';
-import { buildListQuery } from '../helpers/build-list-query';
+import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { patchEntity } from '../helpers/patch-entity';
 
 @Injectable()
 export class CountryService {
-    constructor(@InjectConnection() private connection: Connection) {}
+    constructor(
+        @InjectConnection() private connection: Connection,
+        private listQueryBuilder: ListQueryBuilder,
+    ) {}
 
     findAll(options?: ListQueryOptions<Country>): Promise<PaginatedList<Country>> {
-        return buildListQuery(this.connection, Country, options)
+        return this.listQueryBuilder
+            .build(Country, options)
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,

+ 4 - 2
server/src/service/services/customer.service.ts

@@ -9,7 +9,7 @@ import { Address } from '../../entity/address/address.entity';
 import { Customer } from '../../entity/customer/customer.entity';
 import { User } from '../../entity/user/user.entity';
 import { I18nError } from '../../i18n/i18n-error';
-import { buildListQuery } from '../helpers/build-list-query';
+import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { PasswordCiper } from '../helpers/password-cipher/password-ciper';
 
 import { RoleService } from './role.service';
@@ -20,10 +20,12 @@ export class CustomerService {
         @InjectConnection() private connection: Connection,
         private passwordCipher: PasswordCiper,
         private roleService: RoleService,
+        private listQueryBuilder: ListQueryBuilder,
     ) {}
 
     findAll(options: ListQueryOptions<Customer> | undefined): Promise<PaginatedList<Customer>> {
-        return buildListQuery(this.connection, Customer, options)
+        return this.listQueryBuilder
+            .build(Customer, options)
             .getManyAndCount()
             .then(([items, totalItems]) => ({ items, totalItems }));
     }

+ 4 - 2
server/src/service/services/facet.service.ts

@@ -11,8 +11,8 @@ import { assertFound } from '../../common/utils';
 import { FacetTranslation } from '../../entity/facet/facet-translation.entity';
 import { Facet } from '../../entity/facet/facet.entity';
 
-import { buildListQuery } from '../helpers/build-list-query';
 import { createTranslatable } from '../helpers/create-translatable';
+import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { translateDeep } from '../helpers/translate-entity';
 import { TranslationUpdaterService } from '../helpers/translation-updater.service';
 import { updateTranslatable } from '../helpers/update-translatable';
@@ -22,6 +22,7 @@ export class FacetService {
     constructor(
         @InjectConnection() private connection: Connection,
         private translationUpdaterService: TranslationUpdaterService,
+        private listQueryBuilder: ListQueryBuilder,
     ) {}
 
     findAll(
@@ -30,7 +31,8 @@ export class FacetService {
     ): Promise<PaginatedList<Translated<Facet>>> {
         const relations = ['values'];
 
-        return buildListQuery(this.connection, Facet, options, relations)
+        return this.listQueryBuilder
+            .build(Facet, options, relations)
             .getManyAndCount()
             .then(([facets, totalItems]) => {
                 const items = facets.map(facet => translateDeep(facet, lang, ['values']));

+ 4 - 2
server/src/service/services/order.service.ts

@@ -12,7 +12,7 @@ import { Order } from '../../entity/order/order.entity';
 import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
 import { Promotion } from '../../entity/promotion/promotion.entity';
 import { I18nError } from '../../i18n/i18n-error';
-import { buildListQuery } from '../helpers/build-list-query';
+import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { OrderCalculator } from '../helpers/order-calculator/order-calculator';
 import { translateDeep } from '../helpers/translate-entity';
 
@@ -23,10 +23,12 @@ export class OrderService {
         @InjectConnection() private connection: Connection,
         private productVariantService: ProductVariantService,
         private orderCalculator: OrderCalculator,
+        private listQueryBuilder: ListQueryBuilder,
     ) {}
 
     findAll(ctx: RequestContext, options?: ListQueryOptions<Order>): Promise<PaginatedList<Order>> {
-        return buildListQuery(this.connection, Order, options, ['lines', 'lines.productVariant', 'customer'])
+        return this.listQueryBuilder
+            .build(Order, options, ['lines', 'lines.productVariant', 'customer'])
             .getManyAndCount()
             .then(([items, totalItems]) => {
                 return {

+ 4 - 2
server/src/service/services/product.service.ts

@@ -12,8 +12,8 @@ import { ProductOptionGroup } from '../../entity/product-option-group/product-op
 import { ProductTranslation } from '../../entity/product/product-translation.entity';
 import { Product } from '../../entity/product/product.entity';
 import { I18nError } from '../../i18n/i18n-error';
-import { buildListQuery } from '../helpers/build-list-query';
 import { createTranslatable } from '../helpers/create-translatable';
+import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { translateDeep } from '../helpers/translate-entity';
 import { TranslationUpdaterService } from '../helpers/translation-updater.service';
 import { updateTranslatable } from '../helpers/update-translatable';
@@ -32,6 +32,7 @@ export class ProductService {
         private assetService: AssetService,
         private productVariantService: ProductVariantService,
         private taxRateService: TaxRateService,
+        private listQueryBuilder: ListQueryBuilder,
     ) {}
 
     findAll(
@@ -49,7 +50,8 @@ export class ProductService {
             'channels',
         ];
 
-        return buildListQuery(this.connection, Product, options, relations, ctx.channelId)
+        return this.listQueryBuilder
+            .build(Product, options, relations, ctx.channelId)
             .getManyAndCount()
             .then(async ([products, totalItems]) => {
                 const items = products

+ 4 - 2
server/src/service/services/promotion.service.ts

@@ -18,7 +18,7 @@ import { PromotionAction } from '../../config/promotion/promotion-action';
 import { PromotionCondition } from '../../config/promotion/promotion-condition';
 import { Promotion } from '../../entity/promotion/promotion.entity';
 import { I18nError } from '../../i18n/i18n-error';
-import { buildListQuery } from '../helpers/build-list-query';
+import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { patchEntity } from '../helpers/patch-entity';
 
 import { ChannelService } from './channel.service';
@@ -38,13 +38,15 @@ export class PromotionService {
         @InjectConnection() private connection: Connection,
         private configService: ConfigService,
         private channelService: ChannelService,
+        private listQueryBuilder: ListQueryBuilder,
     ) {
         this.availableConditions = this.configService.promotionConditions;
         this.availableActions = this.configService.promotionActions;
     }
 
     findAll(options?: ListQueryOptions<Promotion>): Promise<PaginatedList<Promotion>> {
-        return buildListQuery(this.connection, Promotion, options)
+        return this.listQueryBuilder
+            .build(Promotion, options)
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,

+ 8 - 3
server/src/service/services/role.service.ts

@@ -14,14 +14,18 @@ import { ListQueryOptions } from '../../common/types/common-types';
 import { assertFound } from '../../common/utils';
 import { Role } from '../../entity/role/role.entity';
 import { I18nError } from '../../i18n/i18n-error';
-import { buildListQuery } from '../helpers/build-list-query';
+import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { patchEntity } from '../helpers/patch-entity';
 
 import { ChannelService } from './channel.service';
 
 @Injectable()
 export class RoleService {
-    constructor(@InjectConnection() private connection: Connection, private channelService: ChannelService) {}
+    constructor(
+        @InjectConnection() private connection: Connection,
+        private channelService: ChannelService,
+        private listQueryBuilder: ListQueryBuilder,
+    ) {}
 
     async initRoles() {
         await this.ensureSuperAdminRoleExists();
@@ -29,7 +33,8 @@ export class RoleService {
     }
 
     findAll(options?: ListQueryOptions<Role>): Promise<PaginatedList<Role>> {
-        return buildListQuery(this.connection, Role, options, ['channels'])
+        return this.listQueryBuilder
+            .build(Role, options, ['channels'])
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,

+ 7 - 3
server/src/service/services/tax-rate.service.ts

@@ -10,8 +10,8 @@ import { TaxCategory } from '../../entity/tax-category/tax-category.entity';
 import { TaxRate } from '../../entity/tax-rate/tax-rate.entity';
 import { Zone } from '../../entity/zone/zone.entity';
 import { I18nError } from '../../i18n/i18n-error';
-import { buildListQuery } from '../helpers/build-list-query';
 import { getEntityOrThrow } from '../helpers/get-entity-or-throw';
+import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
 import { patchEntity } from '../helpers/patch-entity';
 
 export class TaxRateService {
@@ -27,14 +27,18 @@ export class TaxRateService {
         id: '0',
     });
 
-    constructor(@InjectConnection() private connection: Connection) {}
+    constructor(
+        @InjectConnection() private connection: Connection,
+        private listQueryBuilder: ListQueryBuilder,
+    ) {}
 
     async initTaxRates() {
         return this.updateActiveTaxRates();
     }
 
     findAll(options?: ListQueryOptions<TaxRate>): Promise<PaginatedList<TaxRate>> {
-        return buildListQuery(this.connection, TaxRate, options, ['category', 'zone', 'customerGroup'])
+        return this.listQueryBuilder
+            .build(TaxRate, options, ['category', 'zone', 'customerGroup'])
             .getManyAndCount()
             .then(([items, totalItems]) => ({
                 items,