|
|
@@ -16,7 +16,7 @@ import { merge } from 'rxjs';
|
|
|
import { debounceTime } from 'rxjs/operators';
|
|
|
import { Connection } from 'typeorm';
|
|
|
|
|
|
-import { RequestContext } from '../../api/common/request-context';
|
|
|
+import { RequestContext, SerializedRequestContext } from '../../api/common/request-context';
|
|
|
import { configurableDefToOperation } from '../../common/configurable-operation';
|
|
|
import { DEFAULT_LANGUAGE_CODE } from '../../common/constants';
|
|
|
import { IllegalOperationError, UserInputError } from '../../common/error/errors';
|
|
|
@@ -36,6 +36,9 @@ import { EventBus } from '../../event-bus/event-bus';
|
|
|
import { CollectionModificationEvent } from '../../event-bus/events/collection-modification-event';
|
|
|
import { ProductEvent } from '../../event-bus/events/product-event';
|
|
|
import { ProductVariantEvent } from '../../event-bus/events/product-variant-event';
|
|
|
+import { Job } from '../../job-queue/job';
|
|
|
+import { JobQueue } from '../../job-queue/job-queue';
|
|
|
+import { JobQueueService } from '../../job-queue/job-queue.service';
|
|
|
import { WorkerService } from '../../worker/worker.service';
|
|
|
import { ListQueryBuilder } from '../helpers/list-query-builder/list-query-builder';
|
|
|
import { TranslatableSaver } from '../helpers/translatable-saver/translatable-saver';
|
|
|
@@ -43,12 +46,11 @@ import { findOneInChannel } from '../helpers/utils/channel-aware-orm-utils';
|
|
|
import { getEntityOrThrow } from '../helpers/utils/get-entity-or-throw';
|
|
|
import { moveToIndex } from '../helpers/utils/move-to-index';
|
|
|
import { translateDeep } from '../helpers/utils/translate-entity';
|
|
|
-import { ApplyCollectionFiltersMessage } from '../types/collection-messages';
|
|
|
+import { ApplyCollectionFiletersJobData, ApplyCollectionFiltersMessage } from '../types/collection-messages';
|
|
|
|
|
|
import { AssetService } from './asset.service';
|
|
|
import { ChannelService } from './channel.service';
|
|
|
import { FacetValueService } from './facet-value.service';
|
|
|
-import { JobService } from './job.service';
|
|
|
|
|
|
export class CollectionService implements OnModuleInit {
|
|
|
private rootCollection: Collection | undefined;
|
|
|
@@ -56,6 +58,7 @@ export class CollectionService implements OnModuleInit {
|
|
|
facetValueCollectionFilter,
|
|
|
variantNameCollectionFilter,
|
|
|
];
|
|
|
+ private applyFiltersQueue: JobQueue<ApplyCollectionFiletersJobData>;
|
|
|
|
|
|
constructor(
|
|
|
@InjectConnection() private connection: Connection,
|
|
|
@@ -66,7 +69,7 @@ export class CollectionService implements OnModuleInit {
|
|
|
private translatableSaver: TranslatableSaver,
|
|
|
private eventBus: EventBus,
|
|
|
private workerService: WorkerService,
|
|
|
- private jobService: JobService,
|
|
|
+ private jobQueueService: JobQueueService,
|
|
|
) {}
|
|
|
|
|
|
onModuleInit() {
|
|
|
@@ -75,12 +78,24 @@ export class CollectionService implements OnModuleInit {
|
|
|
|
|
|
merge(productEvents$, variantEvents$)
|
|
|
.pipe(debounceTime(50))
|
|
|
- .subscribe(async event => {
|
|
|
- const collections = await this.connection.getRepository(Collection).find({
|
|
|
- relations: ['productVariants'],
|
|
|
+ .subscribe(async (event) => {
|
|
|
+ const collections = await this.connection.getRepository(Collection).find();
|
|
|
+ this.applyFiltersQueue.add({
|
|
|
+ ctx: event.ctx.serialize(),
|
|
|
+ collectionIds: collections.map((c) => c.id),
|
|
|
});
|
|
|
- this.applyCollectionFilters(event.ctx, collections);
|
|
|
});
|
|
|
+
|
|
|
+ this.applyFiltersQueue = this.jobQueueService.createQueue({
|
|
|
+ name: 'apply-collection-filters',
|
|
|
+ concurrency: 1,
|
|
|
+ process: async (job) => {
|
|
|
+ const collections = await this.connection
|
|
|
+ .getRepository(Collection)
|
|
|
+ .findByIds(job.data.collectionIds);
|
|
|
+ this.applyCollectionFilters(job.data.ctx, collections, job);
|
|
|
+ },
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
async findAll(
|
|
|
@@ -98,7 +113,7 @@ export class CollectionService implements OnModuleInit {
|
|
|
})
|
|
|
.getManyAndCount()
|
|
|
.then(async ([collections, totalItems]) => {
|
|
|
- const items = collections.map(collection =>
|
|
|
+ const items = collections.map((collection) =>
|
|
|
translateDeep(collection, ctx.languageCode, ['parent']),
|
|
|
);
|
|
|
return {
|
|
|
@@ -120,7 +135,7 @@ export class CollectionService implements OnModuleInit {
|
|
|
}
|
|
|
|
|
|
getAvailableFilters(ctx: RequestContext): ConfigurableOperationDefinition[] {
|
|
|
- return this.availableFilters.map(x => configurableDefToOperation(ctx, x));
|
|
|
+ return this.availableFilters.map((x) => configurableDefToOperation(ctx, x));
|
|
|
}
|
|
|
|
|
|
async getParent(ctx: RequestContext, collectionId: ID): Promise<Collection | undefined> {
|
|
|
@@ -129,7 +144,7 @@ export class CollectionService implements OnModuleInit {
|
|
|
.createQueryBuilder('collection')
|
|
|
.leftJoinAndSelect('collection.translations', 'translation')
|
|
|
.where(
|
|
|
- qb =>
|
|
|
+ (qb) =>
|
|
|
`collection.id = ${qb
|
|
|
.subQuery()
|
|
|
.select('child.parentId')
|
|
|
@@ -178,7 +193,7 @@ export class CollectionService implements OnModuleInit {
|
|
|
}
|
|
|
const result = await qb.getMany();
|
|
|
|
|
|
- return result.map(collection => translateDeep(collection, ctx.languageCode));
|
|
|
+ return result.map((collection) => translateDeep(collection, ctx.languageCode));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -204,7 +219,7 @@ export class CollectionService implements OnModuleInit {
|
|
|
};
|
|
|
|
|
|
const descendants = await getChildren(rootId);
|
|
|
- return descendants.map(c => translateDeep(c, ctx.languageCode));
|
|
|
+ return descendants.map((c) => translateDeep(c, ctx.languageCode));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -236,9 +251,9 @@ export class CollectionService implements OnModuleInit {
|
|
|
|
|
|
return this.connection
|
|
|
.getRepository(Collection)
|
|
|
- .findByIds(ancestors.map(c => c.id))
|
|
|
- .then(categories => {
|
|
|
- return ctx ? categories.map(c => translateDeep(c, ctx.languageCode)) : categories;
|
|
|
+ .findByIds(ancestors.map((c) => c.id))
|
|
|
+ .then((categories) => {
|
|
|
+ return ctx ? categories.map((c) => translateDeep(c, ctx.languageCode)) : categories;
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -247,7 +262,7 @@ export class CollectionService implements OnModuleInit {
|
|
|
input,
|
|
|
entityType: Collection,
|
|
|
translationType: CollectionTranslation,
|
|
|
- beforeSave: async coll => {
|
|
|
+ beforeSave: async (coll) => {
|
|
|
await this.channelService.assignToCurrentChannel(coll, ctx);
|
|
|
const parent = await this.getParentCollection(ctx, input.parentId);
|
|
|
if (parent) {
|
|
|
@@ -259,7 +274,10 @@ export class CollectionService implements OnModuleInit {
|
|
|
},
|
|
|
});
|
|
|
await this.assetService.updateEntityAssets(collection, input);
|
|
|
- this.applyCollectionFilters(ctx, [collection]);
|
|
|
+ this.applyFiltersQueue.add({
|
|
|
+ ctx: ctx.serialize(),
|
|
|
+ collectionIds: [collection.id],
|
|
|
+ });
|
|
|
return assertFound(this.findOne(ctx, collection.id));
|
|
|
}
|
|
|
|
|
|
@@ -268,7 +286,7 @@ export class CollectionService implements OnModuleInit {
|
|
|
input,
|
|
|
entityType: Collection,
|
|
|
translationType: CollectionTranslation,
|
|
|
- beforeSave: async coll => {
|
|
|
+ beforeSave: async (coll) => {
|
|
|
if (input.filters) {
|
|
|
coll.filters = this.getCollectionFiltersFromInput(input);
|
|
|
}
|
|
|
@@ -277,7 +295,10 @@ export class CollectionService implements OnModuleInit {
|
|
|
},
|
|
|
});
|
|
|
if (input.filters) {
|
|
|
- this.applyCollectionFilters(ctx, [collection]);
|
|
|
+ this.applyFiltersQueue.add({
|
|
|
+ ctx: ctx.serialize(),
|
|
|
+ collectionIds: [collection.id],
|
|
|
+ });
|
|
|
}
|
|
|
return assertFound(this.findOne(ctx, collection.id));
|
|
|
}
|
|
|
@@ -309,7 +330,7 @@ export class CollectionService implements OnModuleInit {
|
|
|
|
|
|
if (
|
|
|
idsAreEqual(input.parentId, target.id) ||
|
|
|
- descendants.some(cat => idsAreEqual(input.parentId, cat.id))
|
|
|
+ descendants.some((cat) => idsAreEqual(input.parentId, cat.id))
|
|
|
) {
|
|
|
throw new IllegalOperationError(`error.cannot-move-collection-into-self`);
|
|
|
}
|
|
|
@@ -328,7 +349,10 @@ export class CollectionService implements OnModuleInit {
|
|
|
siblings = moveToIndex(input.index, target, siblings);
|
|
|
|
|
|
await this.connection.getRepository(Collection).save(siblings);
|
|
|
- await this.applyCollectionFilters(ctx, [target]);
|
|
|
+ this.applyFiltersQueue.add({
|
|
|
+ ctx: ctx.serialize(),
|
|
|
+ collectionIds: [target.id],
|
|
|
+ });
|
|
|
return assertFound(this.findOne(ctx, input.collectionId));
|
|
|
}
|
|
|
|
|
|
@@ -359,37 +383,33 @@ export class CollectionService implements OnModuleInit {
|
|
|
/**
|
|
|
* Applies the CollectionFilters and returns an array of all affected ProductVariant ids.
|
|
|
*/
|
|
|
- private async applyCollectionFilters(ctx: RequestContext, collections: Collection[]): Promise<void> {
|
|
|
- const collectionIds = collections.map(c => c.id);
|
|
|
-
|
|
|
- const job = this.jobService.createJob({
|
|
|
- name: 'apply-collection-filters',
|
|
|
- metadata: { collectionIds },
|
|
|
- singleInstance: false,
|
|
|
- work: async reporter => {
|
|
|
- Logger.verbose(`sending ApplyCollectionFiltersMessage message`);
|
|
|
- this.workerService.send(new ApplyCollectionFiltersMessage({ collectionIds })).subscribe({
|
|
|
- next: ({ total, completed, duration, collectionId, affectedVariantIds }) => {
|
|
|
- const progress = Math.ceil((completed / total) * 100);
|
|
|
- const collection = collections.find(c => idsAreEqual(c.id, collectionId));
|
|
|
- if (collection) {
|
|
|
- this.eventBus.publish(
|
|
|
- new CollectionModificationEvent(ctx, collection, affectedVariantIds),
|
|
|
- );
|
|
|
- }
|
|
|
- reporter.setProgress(progress);
|
|
|
- },
|
|
|
- complete: () => {
|
|
|
- reporter.complete();
|
|
|
- },
|
|
|
- error: err => {
|
|
|
- Logger.error(err);
|
|
|
- reporter.complete();
|
|
|
- },
|
|
|
- });
|
|
|
+ private async applyCollectionFilters(
|
|
|
+ ctx: SerializedRequestContext,
|
|
|
+ collections: Collection[],
|
|
|
+ job: Job<ApplyCollectionFiletersJobData>,
|
|
|
+ ): Promise<void> {
|
|
|
+ const collectionIds = collections.map((c) => c.id);
|
|
|
+ const requestContext = RequestContext.deserialize(ctx);
|
|
|
+
|
|
|
+ this.workerService.send(new ApplyCollectionFiltersMessage({ collectionIds })).subscribe({
|
|
|
+ next: ({ total, completed, duration, collectionId, affectedVariantIds }) => {
|
|
|
+ const progress = Math.ceil((completed / total) * 100);
|
|
|
+ const collection = collections.find((c) => idsAreEqual(c.id, collectionId));
|
|
|
+ if (collection) {
|
|
|
+ this.eventBus.publish(
|
|
|
+ new CollectionModificationEvent(requestContext, collection, affectedVariantIds),
|
|
|
+ );
|
|
|
+ }
|
|
|
+ job.setProgress(progress);
|
|
|
+ },
|
|
|
+ complete: () => {
|
|
|
+ job.complete();
|
|
|
+ },
|
|
|
+ error: (err) => {
|
|
|
+ Logger.error(err);
|
|
|
+ job.fail(err);
|
|
|
},
|
|
|
});
|
|
|
- await job.start();
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -397,14 +417,14 @@ export class CollectionService implements OnModuleInit {
|
|
|
*/
|
|
|
async getCollectionProductVariantIds(collection: Collection): Promise<ID[]> {
|
|
|
if (collection.productVariants) {
|
|
|
- return collection.productVariants.map(v => v.id);
|
|
|
+ return collection.productVariants.map((v) => v.id);
|
|
|
} else {
|
|
|
const productVariants = await this.connection
|
|
|
.getRepository(ProductVariant)
|
|
|
.createQueryBuilder('variant')
|
|
|
.innerJoin('variant.collections', 'collection', 'collection.id = :id', { id: collection.id })
|
|
|
.getMany();
|
|
|
- return productVariants.map(v => v.id);
|
|
|
+ return productVariants.map((v) => v.id);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -483,7 +503,7 @@ export class CollectionService implements OnModuleInit {
|
|
|
}
|
|
|
|
|
|
private getFilterByCode(code: string): CollectionFilter<any> {
|
|
|
- const match = this.availableFilters.find(a => a.code === code);
|
|
|
+ const match = this.availableFilters.find((a) => a.code === code);
|
|
|
if (!match) {
|
|
|
throw new UserInputError(`error.adjustment-operation-with-code-not-found`, { code });
|
|
|
}
|