@deenruv/merchant-plugin 1.0.17-dev.17 → 1.0.17-dev.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/README.md +14 -0
  2. package/dist/plugin-server/api/platform-integration-admin-resolver.d.ts +20 -1
  3. package/dist/plugin-server/api/platform-integration-admin-resolver.js +44 -19
  4. package/dist/plugin-server/entities/merchant-sync-item.entity.d.ts +14 -0
  5. package/dist/plugin-server/entities/merchant-sync-item.entity.js +56 -0
  6. package/dist/plugin-server/entities/merchant-sync-run.entity.d.ts +19 -0
  7. package/dist/plugin-server/entities/merchant-sync-run.entity.js +72 -0
  8. package/dist/plugin-server/extensions/api-extensions.js +53 -2
  9. package/dist/plugin-server/index.js +10 -1
  10. package/dist/plugin-server/services/google-merchant-api.d.ts +48 -6
  11. package/dist/plugin-server/services/google-merchant-api.js +247 -32
  12. package/dist/plugin-server/services/google-platform-integration.service.d.ts +8 -7
  13. package/dist/plugin-server/services/google-platform-integration.service.js +79 -6
  14. package/dist/plugin-server/services/merchant-sync-history.service.d.ts +25 -0
  15. package/dist/plugin-server/services/merchant-sync-history.service.js +97 -0
  16. package/dist/plugin-server/services/platform-integration.service.d.ts +6 -2
  17. package/dist/plugin-server/services/platform-integration.service.js +53 -29
  18. package/dist/plugin-server/services/subscriber.service.d.ts +32 -4
  19. package/dist/plugin-server/services/subscriber.service.js +141 -23
  20. package/dist/plugin-server/ui/graphql/mutations.ts +7 -0
  21. package/dist/plugin-server/ui/graphql/queries.ts +99 -0
  22. package/dist/plugin-server/ui/pages/GooglePage.tsx +158 -16
  23. package/dist/plugin-ui/graphql/mutations.d.ts +5 -0
  24. package/dist/plugin-ui/graphql/mutations.js +6 -0
  25. package/dist/plugin-ui/graphql/queries.d.ts +53 -2
  26. package/dist/plugin-ui/graphql/queries.js +49 -0
  27. package/dist/plugin-ui/pages/GooglePage.js +51 -13
  28. package/package.json +8 -7
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MerchantSyncHistoryService = exports.summarizeGoogleSyncRun = void 0;
13
+ const core_1 = require("@deenruv/core");
14
+ const common_1 = require("@nestjs/common");
15
+ const merchant_sync_item_entity_js_1 = require("../entities/merchant-sync-item.entity.js");
16
+ const merchant_sync_run_entity_js_1 = require("../entities/merchant-sync-run.entity.js");
17
+ const google_merchant_api_js_1 = require("./google-merchant-api.js");
18
+ function summarizeGoogleSyncRun(input) {
19
+ const resultFailures = input.results.filter((result) => result.status === "error").length;
20
+ const total = input.results.length || input.requestedTotal;
21
+ const failed = input.results.length === 0 && input.fallbackError
22
+ ? input.requestedTotal
23
+ : resultFailures;
24
+ const succeeded = Math.max(0, total - failed);
25
+ const status = failed === 0 && !input.fallbackError
26
+ ? "SUCCESS"
27
+ : succeeded > 0
28
+ ? "PARTIAL"
29
+ : "FAILED";
30
+ return { failed, status, succeeded, total };
31
+ }
32
+ exports.summarizeGoogleSyncRun = summarizeGoogleSyncRun;
33
+ let MerchantSyncHistoryService = class MerchantSyncHistoryService {
34
+ constructor(connection) {
35
+ this.connection = connection;
36
+ }
37
+ async start(ctx, input) {
38
+ var _a, _b;
39
+ return this.connection.getRepository(ctx, merchant_sync_run_entity_js_1.MerchantSyncRun).save(new merchant_sync_run_entity_js_1.MerchantSyncRun({
40
+ errorSummary: null,
41
+ failed: 0,
42
+ finishedAt: null,
43
+ items: [],
44
+ jobId: (_a = input.jobId) !== null && _a !== void 0 ? _a : null,
45
+ platform: input.platform,
46
+ startedAt: new Date(),
47
+ status: "RUNNING",
48
+ succeeded: 0,
49
+ total: (_b = input.total) !== null && _b !== void 0 ? _b : 0,
50
+ trigger: input.trigger,
51
+ }));
52
+ }
53
+ async finishGoogleRun(ctx, run, results, fallbackError) {
54
+ var _a, _b;
55
+ const itemRepository = this.connection.getRepository(ctx, merchant_sync_item_entity_js_1.MerchantSyncItem);
56
+ const items = results.map((result) => {
57
+ var _a, _b;
58
+ const normalized = result.status === "error"
59
+ ? (0, google_merchant_api_js_1.normalizeGoogleMerchantError)(result.error)
60
+ : undefined;
61
+ return new merchant_sync_item_entity_js_1.MerchantSyncItem({
62
+ attempts: result.attempts,
63
+ errorCode: (_a = normalized === null || normalized === void 0 ? void 0 : normalized.code) !== null && _a !== void 0 ? _a : null,
64
+ errorMessage: (_b = normalized === null || normalized === void 0 ? void 0 : normalized.message) !== null && _b !== void 0 ? _b : null,
65
+ offerId: result.item.communicateID,
66
+ operation: result.item.method,
67
+ run,
68
+ status: result.status === "success" ? "SUCCESS" : "FAILED",
69
+ });
70
+ });
71
+ if (items.length > 0)
72
+ await itemRepository.save(items, { chunk: 100 });
73
+ const summary = summarizeGoogleSyncRun({
74
+ fallbackError,
75
+ requestedTotal: run.total,
76
+ results,
77
+ });
78
+ const fallback = fallbackError
79
+ ? (0, google_merchant_api_js_1.normalizeGoogleMerchantError)(fallbackError)
80
+ : undefined;
81
+ const firstItemError = items.find((item) => item.errorMessage);
82
+ return this.connection.getRepository(ctx, merchant_sync_run_entity_js_1.MerchantSyncRun).save(Object.assign(Object.assign({}, run), { errorSummary: (_b = (_a = fallback === null || fallback === void 0 ? void 0 : fallback.message) !== null && _a !== void 0 ? _a : firstItemError === null || firstItemError === void 0 ? void 0 : firstItemError.errorMessage) !== null && _b !== void 0 ? _b : null, failed: summary.failed, finishedAt: new Date(), status: summary.status, succeeded: summary.succeeded, total: summary.total }));
83
+ }
84
+ async findLatest(ctx, platform, take = 20) {
85
+ return this.connection.getRepository(ctx, merchant_sync_run_entity_js_1.MerchantSyncRun).find({
86
+ order: { createdAt: "DESC" },
87
+ relations: ["items"],
88
+ take: Math.min(Math.max(take, 1), 100),
89
+ where: { platform },
90
+ });
91
+ }
92
+ };
93
+ exports.MerchantSyncHistoryService = MerchantSyncHistoryService;
94
+ exports.MerchantSyncHistoryService = MerchantSyncHistoryService = __decorate([
95
+ (0, common_1.Injectable)(),
96
+ __metadata("design:paramtypes", [core_1.TransactionalConnection])
97
+ ], MerchantSyncHistoryService);
@@ -4,6 +4,7 @@ import { MerchantPlatformSettingsEntity } from "../entities/platform-integration
4
4
  import { FacebookPlatformIntegrationService } from "./facebook-platform-integration.service.js";
5
5
  import { GooglePlatformIntegrationService } from "./google-platform-integration.service.js";
6
6
  import { MerchantStrategyService } from "./merchant-strategy.service.js";
7
+ import { MerchantSyncHistoryService } from "./merchant-sync-history.service.js";
7
8
  type JOB_PAYLOAD = {
8
9
  platform: string;
9
10
  action: "SEND_ALL_PRODUCTS";
@@ -15,6 +16,7 @@ export declare class PlatformIntegrationService implements OnModuleInit {
15
16
  private readonly facebookService;
16
17
  private readonly productService;
17
18
  private readonly strategy;
19
+ private readonly syncHistory;
18
20
  MerchantPlatformQueue: JobQueue<{
19
21
  payload: JOB_PAYLOAD;
20
22
  worker: number;
@@ -24,7 +26,7 @@ export declare class PlatformIntegrationService implements OnModuleInit {
24
26
  }>;
25
27
  private readonly logger;
26
28
  private log;
27
- constructor(connection: TransactionalConnection, jobQueueService: JobQueueService, googleService: GooglePlatformIntegrationService, facebookService: FacebookPlatformIntegrationService, productService: ProductService, strategy: MerchantStrategyService);
29
+ constructor(connection: TransactionalConnection, jobQueueService: JobQueueService, googleService: GooglePlatformIntegrationService, facebookService: FacebookPlatformIntegrationService, productService: ProductService, strategy: MerchantStrategyService, syncHistory: MerchantSyncHistoryService);
28
30
  removeOrphanItems(ctx: RequestContext, platform: string): Promise<boolean>;
29
31
  fetchProducts(input: {
30
32
  ctx: RequestContext;
@@ -32,8 +34,10 @@ export declare class PlatformIntegrationService implements OnModuleInit {
32
34
  }, callback?: (progress: number) => void): AsyncGenerator<import("../types.js").BaseProductData<import("../types.js").BaseData>, void, unknown>;
33
35
  onModuleInit(): Promise<void>;
34
36
  createContext(): Promise<RequestContext>;
35
- savePlatformIntegrationSettings(ctx: RequestContext, settings: MerchantPlatformSettingsEntity): Promise<MerchantPlatformSettingsEntity | undefined>;
37
+ savePlatformIntegrationSettings(ctx: RequestContext, settings: MerchantPlatformSettingsEntity): Promise<MerchantPlatformSettingsEntity>;
36
38
  getBaseSettings(ctx: RequestContext, platform: string): Promise<MerchantPlatformSettingsEntity | null>;
39
+ enqueueFullSync(ctx: RequestContext, platform: string): Promise<boolean>;
40
+ getSyncHistory(ctx: RequestContext, platform: string, take?: number): Promise<import("../entities/merchant-sync-run.entity.js").MerchantSyncRun[]>;
37
41
  getPlatformAutoUpdateSettings(ctx: RequestContext): Promise<{
38
42
  googleAutoUpdate: boolean;
39
43
  facebookAutoUpdate: boolean;
@@ -38,22 +38,24 @@ const facebook_platform_integration_service_js_1 = require("./facebook-platform-
38
38
  const google_merchant_api_js_1 = require("./google-merchant-api.js");
39
39
  const google_platform_integration_service_js_1 = require("./google-platform-integration.service.js");
40
40
  const merchant_strategy_service_js_1 = require("./merchant-strategy.service.js");
41
+ const merchant_sync_history_service_js_1 = require("./merchant-sync-history.service.js");
41
42
  const BATCH_SIZE = 200;
42
43
  const WORKER_THRESHOLD = 5000;
43
44
  let PlatformIntegrationService = class PlatformIntegrationService {
44
- constructor(connection, jobQueueService, googleService, facebookService, productService, strategy) {
45
+ constructor(connection, jobQueueService, googleService, facebookService, productService, strategy, syncHistory) {
45
46
  this.connection = connection;
46
47
  this.jobQueueService = jobQueueService;
47
48
  this.googleService = googleService;
48
49
  this.facebookService = facebookService;
49
50
  this.productService = productService;
50
51
  this.strategy = strategy;
52
+ this.syncHistory = syncHistory;
51
53
  this.logger = new core_1.Logger();
52
54
  this.log = (message) => this.logger.log(message, "Merchant Platform Service");
53
55
  }
54
56
  async removeOrphanItems(ctx, platform) {
55
57
  if (["facebook", "google"].includes(platform)) {
56
- await this.OrphanItemsQueue.add({ platform });
58
+ await this.OrphanItemsQueue.add({ platform }, { retries: 3 });
57
59
  return true;
58
60
  }
59
61
  return false;
@@ -65,13 +67,17 @@ let PlatformIntegrationService = class PlatformIntegrationService {
65
67
  const start = worker * WORKER_THRESHOLD;
66
68
  for (let i = start; i < totalToFetch; i += BATCH_SIZE) {
67
69
  const { items } = yield __await(this.productService.findAll(ctx, {
70
+ sort: { id: admin_types_1.SortOrder.ASC },
68
71
  take: BATCH_SIZE,
69
72
  skip: i,
70
73
  }));
74
+ if (items.length === 0)
75
+ break;
71
76
  for (const product of items) {
72
77
  const baseProduct = yield __await(this.strategy.getBaseData(ctx, product));
73
78
  if (callback) {
74
- const progress = Math.floor((i / totalToFetch) * 100);
79
+ const progress = Math.min(100, Math.floor(((i - start + items.indexOf(product) + 1) / WORKER_THRESHOLD) *
80
+ 100));
75
81
  callback(progress);
76
82
  }
77
83
  if (!baseProduct)
@@ -117,10 +123,17 @@ let PlatformIntegrationService = class PlatformIntegrationService {
117
123
  let googleResponse = false;
118
124
  let facebookResponse = false;
119
125
  if (platform === "google") {
126
+ const run = await this.syncHistory.start(ctx, {
127
+ jobId: job.id == null ? undefined : String(job.id),
128
+ platform,
129
+ total: products.length,
130
+ trigger: "FULL_SYNC",
131
+ });
120
132
  const response = await this.googleService.batchProductsAction({
121
133
  ctx,
122
134
  products,
123
135
  });
136
+ await this.syncHistory.finishGoogleRun(ctx, run, response.results, response.error);
124
137
  if (response.status === "success") {
125
138
  this.log("Products sent to google");
126
139
  googleResponse = true;
@@ -229,36 +242,24 @@ let PlatformIntegrationService = class PlatformIntegrationService {
229
242
  });
230
243
  }
231
244
  async savePlatformIntegrationSettings(ctx, settings) {
232
- const repository = this.connection.getRepository(ctx, platform_integration_settings_entity_js_1.MerchantPlatformSettingsEntity);
233
- const existing = await repository.findOne({
234
- where: { platform: settings.platform },
235
- });
236
- if (existing)
237
- await repository.delete(existing.id);
238
- let response = await repository.save(settings);
239
- if (!response.id)
240
- return;
241
245
  const [isFirstSync, isAutoUpdate] = [
242
246
  this.lookup(settings, "firstSync") === "true",
243
247
  this.lookup(settings, "autoUpdate") === "true",
244
248
  ];
245
- if (isFirstSync && isAutoUpdate) {
246
- const products = await this.productService.findAll(ctx, {
247
- take: 1,
248
- skip: 0,
249
+ const shouldStartFullSync = isFirstSync && isAutoUpdate;
250
+ const settingsToSave = new platform_integration_settings_entity_js_1.MerchantPlatformSettingsEntity(Object.assign(Object.assign({}, settings), { entries: settings.entries.map((entry) => shouldStartFullSync && entry.key === "firstSync"
251
+ ? Object.assign(Object.assign({}, entry), { value: "false" }) : entry) }));
252
+ const response = await this.connection.withTransaction(ctx, async (transactionCtx) => {
253
+ const repository = this.connection.getRepository(transactionCtx, platform_integration_settings_entity_js_1.MerchantPlatformSettingsEntity);
254
+ const existing = await repository.findOne({
255
+ where: { platform: settings.platform },
249
256
  });
250
- const total = products.totalItems;
251
- const workers = Math.ceil(total / WORKER_THRESHOLD);
252
- for (let worker = 0; worker < workers; worker++) {
253
- await this.MerchantPlatformQueue.add({
254
- worker,
255
- payload: {
256
- platform: response.platform,
257
- action: "SEND_ALL_PRODUCTS",
258
- },
259
- });
260
- }
261
- response = await repository.save(Object.assign(Object.assign({}, settings), { entries: settings.entries.map((entry) => entry.key === "firstSync" ? Object.assign(Object.assign({}, entry), { value: "false" }) : entry) }));
257
+ if (existing)
258
+ await repository.delete(existing.id);
259
+ return repository.save(settingsToSave);
260
+ });
261
+ if (response.id && shouldStartFullSync) {
262
+ await this.enqueueFullSync(ctx, response.platform);
262
263
  }
263
264
  return response;
264
265
  }
@@ -267,6 +268,28 @@ let PlatformIntegrationService = class PlatformIntegrationService {
267
268
  .getRepository(ctx, platform_integration_settings_entity_js_1.MerchantPlatformSettingsEntity)
268
269
  .findOne({ relations: ["entries"], where: { platform } });
269
270
  }
271
+ async enqueueFullSync(ctx, platform) {
272
+ if (!["google", "facebook"].includes(platform))
273
+ return false;
274
+ const { totalItems } = await this.productService.findAll(ctx, {
275
+ take: 1,
276
+ skip: 0,
277
+ });
278
+ const workers = Math.max(1, Math.ceil(totalItems / WORKER_THRESHOLD));
279
+ for (let worker = 0; worker < workers; worker += 1) {
280
+ await this.MerchantPlatformQueue.add({
281
+ worker,
282
+ payload: {
283
+ platform,
284
+ action: "SEND_ALL_PRODUCTS",
285
+ },
286
+ }, { retries: 3 });
287
+ }
288
+ return true;
289
+ }
290
+ getSyncHistory(ctx, platform, take) {
291
+ return this.syncHistory.findLatest(ctx, platform, take);
292
+ }
270
293
  async getPlatformAutoUpdateSettings(ctx) {
271
294
  const [googlePlatformSettings, facebookPlatformSettings] = await Promise.all([
272
295
  this.getBaseSettings(ctx, "google"),
@@ -290,5 +313,6 @@ exports.PlatformIntegrationService = PlatformIntegrationService = __decorate([
290
313
  google_platform_integration_service_js_1.GooglePlatformIntegrationService,
291
314
  facebook_platform_integration_service_js_1.FacebookPlatformIntegrationService,
292
315
  core_1.ProductService,
293
- merchant_strategy_service_js_1.MerchantStrategyService])
316
+ merchant_strategy_service_js_1.MerchantStrategyService,
317
+ merchant_sync_history_service_js_1.MerchantSyncHistoryService])
294
318
  ], PlatformIntegrationService);
@@ -1,13 +1,34 @@
1
1
  import { CreateProductInput, CreateProductVariantInput, UpdateProductInput, UpdateProductVariantInput } from "@deenruv/common/lib/generated-types.js";
2
- import { EventBus, ID, Product, ProductService, RequestContext } from "@deenruv/core";
2
+ import { EventBus, ID, JobQueueService, Product, ProductService, RequestContext, TransactionalConnection } from "@deenruv/core";
3
3
  import { OnApplicationBootstrap } from "@nestjs/common";
4
4
  import { MerchantPluginOptions } from "../types.js";
5
5
  import { FacebookPlatformIntegrationService } from "./facebook-platform-integration.service.js";
6
6
  import { GooglePlatformIntegrationService } from "./google-platform-integration.service.js";
7
7
  import { MerchantStrategyService } from "./merchant-strategy.service.js";
8
+ import { MerchantSyncHistoryService } from "./merchant-sync-history.service.js";
8
9
  import { PlatformIntegrationService } from "./platform-integration.service.js";
9
10
  type ProductInputTypes = CreateProductInput | UpdateProductInput | ID;
10
11
  type ProductVariantInputTypes = CreateProductVariantInput[] | UpdateProductVariantInput[] | ID | ID[];
12
+ export declare function getMerchantOperationsForEvent(type: "created" | "updated" | "deleted"): {
13
+ readonly google: "insertProduct";
14
+ readonly facebook: "createProduct";
15
+ } | {
16
+ readonly google: "insertProduct";
17
+ readonly facebook: "updateProduct";
18
+ } | {
19
+ readonly google: "deleteProduct";
20
+ readonly facebook: "deleteProduct";
21
+ };
22
+ export declare function getGoogleDeleteIds(product: {
23
+ variants?: Array<{
24
+ customFields?: {
25
+ communicateID?: string | null;
26
+ };
27
+ sku?: string;
28
+ }>;
29
+ }, data?: Array<{
30
+ communicateID: string;
31
+ }>): string[];
11
32
  export declare class SubscriberService implements OnApplicationBootstrap {
12
33
  private readonly options;
13
34
  private readonly eventBus;
@@ -16,15 +37,22 @@ export declare class SubscriberService implements OnApplicationBootstrap {
16
37
  private readonly facebookService;
17
38
  private readonly strategy;
18
39
  private readonly productService;
19
- constructor(options: MerchantPluginOptions, eventBus: EventBus, integrationService: PlatformIntegrationService, googleService: GooglePlatformIntegrationService, facebookService: FacebookPlatformIntegrationService, strategy: MerchantStrategyService, productService: ProductService);
40
+ private readonly jobQueueService;
41
+ private readonly syncHistory;
42
+ private readonly connection;
43
+ private readonly logger;
44
+ private productEventQueue;
45
+ constructor(options: MerchantPluginOptions, eventBus: EventBus, integrationService: PlatformIntegrationService, googleService: GooglePlatformIntegrationService, facebookService: FacebookPlatformIntegrationService, strategy: MerchantStrategyService, productService: ProductService, jobQueueService: JobQueueService, syncHistory: MerchantSyncHistoryService, connection: TransactionalConnection);
20
46
  onApplicationBootstrap(): Promise<void>;
21
- event({ ctx, entity, type, input, }: {
47
+ event({ ctx, entity, type, input, deleteIds, }: {
22
48
  ctx: RequestContext;
23
49
  entity: Product;
24
50
  type: "created" | "updated" | "deleted";
25
51
  input?: ProductInputTypes | ProductVariantInputTypes;
52
+ deleteIds?: string[];
26
53
  }): Promise<void>;
54
+ private processEvent;
27
55
  private isValidEventData;
28
- private getOperationsForType;
56
+ private observeOperations;
29
57
  }
30
58
  export {};
@@ -12,16 +12,41 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
12
  return function (target, key) { decorator(target, key, paramIndex); }
13
13
  };
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
- exports.SubscriberService = void 0;
15
+ exports.SubscriberService = exports.getGoogleDeleteIds = exports.getMerchantOperationsForEvent = void 0;
16
16
  const core_1 = require("@deenruv/core");
17
17
  const common_1 = require("@nestjs/common");
18
18
  const constants_js_1 = require("../constants.js");
19
19
  const facebook_platform_integration_service_js_1 = require("./facebook-platform-integration.service.js");
20
20
  const google_platform_integration_service_js_1 = require("./google-platform-integration.service.js");
21
21
  const merchant_strategy_service_js_1 = require("./merchant-strategy.service.js");
22
+ const merchant_sync_history_service_js_1 = require("./merchant-sync-history.service.js");
22
23
  const platform_integration_service_js_1 = require("./platform-integration.service.js");
24
+ function getMerchantOperationsForEvent(type) {
25
+ const operationMap = {
26
+ created: { google: "insertProduct", facebook: "createProduct" },
27
+ updated: { google: "insertProduct", facebook: "updateProduct" },
28
+ deleted: { google: "deleteProduct", facebook: "deleteProduct" },
29
+ };
30
+ return operationMap[type];
31
+ }
32
+ exports.getMerchantOperationsForEvent = getMerchantOperationsForEvent;
33
+ function getGoogleDeleteIds(product, data) {
34
+ var _a, _b, _c;
35
+ const ids = new Set();
36
+ for (const variant of (_a = product.variants) !== null && _a !== void 0 ? _a : []) {
37
+ const id = (_c = (_b = variant.customFields) === null || _b === void 0 ? void 0 : _b.communicateID) !== null && _c !== void 0 ? _c : variant.sku;
38
+ if (id)
39
+ ids.add(String(id));
40
+ }
41
+ for (const item of data !== null && data !== void 0 ? data : []) {
42
+ if (item.communicateID)
43
+ ids.add(String(item.communicateID));
44
+ }
45
+ return [...ids];
46
+ }
47
+ exports.getGoogleDeleteIds = getGoogleDeleteIds;
23
48
  let SubscriberService = class SubscriberService {
24
- constructor(options, eventBus, integrationService, googleService, facebookService, strategy, productService) {
49
+ constructor(options, eventBus, integrationService, googleService, facebookService, strategy, productService, jobQueueService, syncHistory, connection) {
25
50
  this.options = options;
26
51
  this.eventBus = eventBus;
27
52
  this.integrationService = integrationService;
@@ -29,8 +54,20 @@ let SubscriberService = class SubscriberService {
29
54
  this.facebookService = facebookService;
30
55
  this.strategy = strategy;
31
56
  this.productService = productService;
57
+ this.jobQueueService = jobQueueService;
58
+ this.syncHistory = syncHistory;
59
+ this.connection = connection;
60
+ this.logger = new core_1.Logger();
32
61
  }
33
62
  async onApplicationBootstrap() {
63
+ this.productEventQueue = await this.jobQueueService.createQueue({
64
+ name: "MerchantProductEventQueue",
65
+ process: async (job) => {
66
+ const ctx = core_1.RequestContext.deserialize(job.data.ctx);
67
+ const product = await this.productService.findOne(ctx, job.data.productId);
68
+ await this.processEvent(ctx, product !== null && product !== void 0 ? product : undefined, job.data.type, job.data.deleteIds, job.id == null ? undefined : String(job.id));
69
+ },
70
+ });
34
71
  this.eventBus.ofType(core_1.ProductEvent).subscribe(async (props) => {
35
72
  await this.event(props);
36
73
  });
@@ -42,39 +79,117 @@ let SubscriberService = class SubscriberService {
42
79
  const product = await this.productService.findOne(ctx, entity[0].productId);
43
80
  if (!product)
44
81
  return;
45
- await this.event({ ctx, entity: product, type, input });
82
+ await this.event({
83
+ ctx,
84
+ deleteIds: getGoogleDeleteIds({ variants: entity }),
85
+ entity: product,
86
+ type,
87
+ input,
88
+ });
46
89
  });
47
90
  }
48
- async event({ ctx, entity, type, input, }) {
91
+ async event({ ctx, entity, type, input, deleteIds, }) {
49
92
  if (!this.isValidEventData(ctx, entity))
50
93
  return;
94
+ const resolvedDeleteIds = new Set(deleteIds !== null && deleteIds !== void 0 ? deleteIds : getGoogleDeleteIds(entity));
95
+ if (type === "deleted" || resolvedDeleteIds.size === 0) {
96
+ const variants = await this.connection
97
+ .getRepository(ctx, core_1.ProductVariant)
98
+ .find({
99
+ relations: { product: true },
100
+ where: { product: { id: entity.id } },
101
+ withDeleted: true,
102
+ });
103
+ for (const id of getGoogleDeleteIds({ variants })) {
104
+ resolvedDeleteIds.add(id);
105
+ }
106
+ }
107
+ await this.productEventQueue.add({
108
+ ctx: ctx.serialize(),
109
+ deleteIds: [...resolvedDeleteIds],
110
+ productId: entity.id,
111
+ type,
112
+ }, { retries: 3 });
113
+ }
114
+ async processEvent(ctx, entity, type, persistedDeleteIds, jobId) {
51
115
  const { googleAutoUpdate, facebookAutoUpdate } = await this.integrationService.getPlatformAutoUpdateSettings(ctx);
52
116
  if (!googleAutoUpdate && !facebookAutoUpdate)
53
117
  return;
54
- const data = await this.strategy.getBaseData(ctx, entity);
55
- if (!data)
118
+ const data = entity && type !== "deleted"
119
+ ? await this.strategy.getBaseData(ctx, entity)
120
+ : undefined;
121
+ const hasExportableData = Boolean(data === null || data === void 0 ? void 0 : data.length);
122
+ if (type === "deleted" || !hasExportableData) {
123
+ if (googleAutoUpdate) {
124
+ const deleteIds = [
125
+ ...new Set([
126
+ ...persistedDeleteIds,
127
+ ...(entity ? getGoogleDeleteIds(entity, data) : []),
128
+ ]),
129
+ ];
130
+ const run = await this.syncHistory.start(ctx, {
131
+ jobId,
132
+ platform: "google",
133
+ total: deleteIds.length,
134
+ trigger: "PRODUCT_EVENT",
135
+ });
136
+ const response = await this.googleService.deleteProductsByCommunicationIds(ctx, deleteIds);
137
+ await this.syncHistory.finishGoogleRun(ctx, run, response.results, response.error);
138
+ if (response.status === "error") {
139
+ throw response.error instanceof Error
140
+ ? response.error
141
+ : new Error("Google product deletion failed");
142
+ }
143
+ }
144
+ if (facebookAutoUpdate && (data === null || data === void 0 ? void 0 : data.length) && entity) {
145
+ await this.facebookService.deleteProduct({ ctx, data, entity });
146
+ }
147
+ return;
148
+ }
149
+ if (!data || !entity)
56
150
  return;
57
- const operations = this.getOperationsForType(type);
151
+ const operations = getMerchantOperationsForEvent(type);
58
152
  const eventPayload = { ctx, data, entity };
59
- await Promise.allSettled([
60
- ...(googleAutoUpdate
61
- ? [this.googleService[operations.google](eventPayload)]
62
- : []),
63
- ...(facebookAutoUpdate
64
- ? [this.facebookService[operations.facebook](eventPayload)]
65
- : []),
66
- ]);
153
+ if (googleAutoUpdate) {
154
+ const run = await this.syncHistory.start(ctx, {
155
+ jobId,
156
+ platform: "google",
157
+ total: data.length,
158
+ trigger: "PRODUCT_EVENT",
159
+ });
160
+ const response = await this.googleService[operations.google](eventPayload);
161
+ await this.syncHistory.finishGoogleRun(ctx, run, response.results, response.error);
162
+ if (response.status === "error") {
163
+ throw response.error instanceof Error
164
+ ? response.error
165
+ : new Error("Google product synchronization failed");
166
+ }
167
+ }
168
+ if (facebookAutoUpdate) {
169
+ await this.observeOperations([this.facebookService[operations.facebook](eventPayload)], type);
170
+ }
67
171
  }
68
172
  isValidEventData(ctx, entity) {
69
173
  return Boolean(ctx && entity && entity instanceof core_1.Product);
70
174
  }
71
- getOperationsForType(type) {
72
- const operationMap = {
73
- created: { google: "insertProduct", facebook: "createProduct" },
74
- updated: { google: "updateProduct", facebook: "updateProduct" },
75
- deleted: { google: "deleteProduct", facebook: "deleteProduct" },
76
- };
77
- return operationMap[type];
175
+ async observeOperations(operations, eventType) {
176
+ const results = await Promise.allSettled(operations);
177
+ for (const result of results) {
178
+ if (result.status === "rejected") {
179
+ this.logger.error(`Merchant ${eventType} operation rejected`, result.reason instanceof Error
180
+ ? result.reason.stack
181
+ : String(result.reason), "Merchant Product Subscriber");
182
+ continue;
183
+ }
184
+ const value = result.value;
185
+ if (typeof value === "object" &&
186
+ value !== null &&
187
+ "status" in value &&
188
+ value.status === "error") {
189
+ const error = "error" in value ? value.error : undefined;
190
+ this.logger.error(`Merchant ${eventType} operation failed`, error instanceof Error ? error.stack : String(error), "Merchant Product Subscriber");
191
+ }
192
+ }
78
193
  }
79
194
  };
80
195
  exports.SubscriberService = SubscriberService;
@@ -86,5 +201,8 @@ exports.SubscriberService = SubscriberService = __decorate([
86
201
  google_platform_integration_service_js_1.GooglePlatformIntegrationService,
87
202
  facebook_platform_integration_service_js_1.FacebookPlatformIntegrationService,
88
203
  merchant_strategy_service_js_1.MerchantStrategyService,
89
- core_1.ProductService])
204
+ core_1.ProductService,
205
+ core_1.JobQueueService,
206
+ merchant_sync_history_service_js_1.MerchantSyncHistoryService,
207
+ core_1.TransactionalConnection])
90
208
  ], SubscriberService);
@@ -21,3 +21,10 @@ export const saveMerchantPlatformSettings = mutation({
21
21
  export const removeOrphanItems = mutation({
22
22
  removeOrphanItems: [{ platform: $("platform", "String!") }, true],
23
23
  });
24
+
25
+ export const sendAllProductsToMerchantPlatform = mutation({
26
+ sendAllProductsToMerchantPlatform: [
27
+ { platform: $("platform", "String!") },
28
+ true,
29
+ ],
30
+ });