collection.controller.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import { Controller } from '@nestjs/common';
  2. import { MessagePattern } from '@nestjs/microservices';
  3. import { InjectConnection } from '@nestjs/typeorm';
  4. import { ConfigurableOperation } from '@vendure/common/lib/generated-types';
  5. import { ID } from '@vendure/common/lib/shared-types';
  6. import { notNullOrUndefined } from '@vendure/common/lib/shared-utils';
  7. import { Observable } from 'rxjs';
  8. import { Connection } from 'typeorm';
  9. import {
  10. facetValueCollectionFilter,
  11. variantNameCollectionFilter,
  12. } from '../../config/collection/default-collection-filters';
  13. import { Logger } from '../../config/logger/vendure-logger';
  14. import { Collection } from '../../entity/collection/collection.entity';
  15. import { ProductVariant } from '../../entity/product-variant/product-variant.entity';
  16. import { CollectionService } from '../services/collection.service';
  17. import { ApplyCollectionFiltersMessage } from '../types/collection-messages';
  18. /**
  19. * Updates collections on the worker process because running the CollectionFilters
  20. * is computationally expensive.
  21. */
  22. @Controller()
  23. export class CollectionController {
  24. constructor(
  25. @InjectConnection() private connection: Connection,
  26. private collectionService: CollectionService,
  27. ) {}
  28. @MessagePattern(ApplyCollectionFiltersMessage.pattern)
  29. applyCollectionFilters({
  30. collectionIds,
  31. }: ApplyCollectionFiltersMessage['data']): Observable<ApplyCollectionFiltersMessage['response']> {
  32. return new Observable(observer => {
  33. (async () => {
  34. Logger.verbose(`Processing ${collectionIds.length} Collections`);
  35. const timeStart = Date.now();
  36. const collections = await this.connection.getRepository(Collection).findByIds(collectionIds, {
  37. relations: ['productVariants'],
  38. });
  39. let completed = 0;
  40. for (const collection of collections) {
  41. const affectedVariantIds = await this.applyCollectionFiltersInternal(collection);
  42. observer.next({
  43. total: collectionIds.length,
  44. completed: ++completed,
  45. duration: +new Date() - timeStart,
  46. collectionId: collection.id,
  47. affectedVariantIds,
  48. });
  49. }
  50. observer.complete();
  51. })();
  52. });
  53. }
  54. /**
  55. * Applies the CollectionFilters and returns an array of all affected ProductVariant ids.
  56. */
  57. private async applyCollectionFiltersInternal(collection: Collection): Promise<ID[]> {
  58. const ancestorFilters = await this.collectionService
  59. .getAncestors(collection.id)
  60. .then(ancestors =>
  61. ancestors.reduce(
  62. (filters, c) => [...filters, ...(c.filters || [])],
  63. [] as ConfigurableOperation[],
  64. ),
  65. );
  66. const preIds = await this.collectionService.getCollectionProductVariantIds(collection);
  67. collection.productVariants = await this.getFilteredProductVariants([
  68. ...ancestorFilters,
  69. ...(collection.filters || []),
  70. ]);
  71. const postIds = collection.productVariants.map(v => v.id);
  72. await this.connection
  73. .getRepository(Collection)
  74. .save(collection, { chunk: Math.ceil(collection.productVariants.length / 500) });
  75. const preIdsSet = new Set(preIds);
  76. const postIdsSet = new Set(postIds);
  77. const difference = [
  78. ...preIds.filter(id => !postIdsSet.has(id)),
  79. ...postIds.filter(id => !preIdsSet.has(id)),
  80. ];
  81. return difference;
  82. }
  83. /**
  84. * Applies the CollectionFilters and returns an array of ProductVariant entities which match.
  85. */
  86. private async getFilteredProductVariants(filters: ConfigurableOperation[]): Promise<ProductVariant[]> {
  87. if (filters.length === 0) {
  88. return [];
  89. }
  90. const facetFilters = filters.filter(f => f.code === facetValueCollectionFilter.code);
  91. const variantNameFilters = filters.filter(f => f.code === variantNameCollectionFilter.code);
  92. let qb = this.connection.getRepository(ProductVariant).createQueryBuilder('productVariant');
  93. // Apply any facetValue-based filters
  94. if (facetFilters.length) {
  95. const [idsArg, containsAnyArg] = facetFilters[0].args;
  96. const mergedArgs = facetFilters
  97. .map(f => f.args[0].value)
  98. .filter(notNullOrUndefined)
  99. .map(value => JSON.parse(value))
  100. .reduce((all, ids) => [...all, ...ids]);
  101. qb = facetValueCollectionFilter.apply(qb, [
  102. {
  103. name: idsArg.name,
  104. type: idsArg.type,
  105. value: JSON.stringify(Array.from(new Set(mergedArgs))),
  106. },
  107. containsAnyArg,
  108. ]);
  109. }
  110. // Apply any variant name-based filters
  111. if (variantNameFilters.length) {
  112. for (const filter of variantNameFilters) {
  113. qb = variantNameCollectionFilter.apply(qb, filter.args);
  114. }
  115. }
  116. return qb.getMany();
  117. }
  118. }