@deenruv/merchant-plugin 1.0.0

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 (78) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +55 -0
  3. package/dist/plugin-server/api/platform-integration-admin-resolver.d.ts +26 -0
  4. package/dist/plugin-server/api/platform-integration-admin-resolver.js +103 -0
  5. package/dist/plugin-server/constants.d.ts +1 -0
  6. package/dist/plugin-server/constants.js +4 -0
  7. package/dist/plugin-server/e2e/plugin.e2e.test.d.ts +1 -0
  8. package/dist/plugin-server/e2e/plugin.e2e.test.js +36 -0
  9. package/dist/plugin-server/entities/platform-integration-setting.entity.d.ts +10 -0
  10. package/dist/plugin-server/entities/platform-integration-setting.entity.js +37 -0
  11. package/dist/plugin-server/entities/platform-integration-settings.entity.d.ts +9 -0
  12. package/dist/plugin-server/entities/platform-integration-settings.entity.js +33 -0
  13. package/dist/plugin-server/extensions/api-extensions.d.ts +2 -0
  14. package/dist/plugin-server/extensions/api-extensions.js +49 -0
  15. package/dist/plugin-server/index.d.ts +14 -0
  16. package/dist/plugin-server/index.js +100 -0
  17. package/dist/plugin-server/services/facebook-platform-integration.service.d.ts +51 -0
  18. package/dist/plugin-server/services/facebook-platform-integration.service.js +219 -0
  19. package/dist/plugin-server/services/google-platform-integration.service.d.ts +63 -0
  20. package/dist/plugin-server/services/google-platform-integration.service.js +352 -0
  21. package/dist/plugin-server/services/merchant-strategy.service.d.ts +10 -0
  22. package/dist/plugin-server/services/merchant-strategy.service.js +39 -0
  23. package/dist/plugin-server/services/platform-integration.service.d.ts +43 -0
  24. package/dist/plugin-server/services/platform-integration.service.js +282 -0
  25. package/dist/plugin-server/services/subscriber.service.d.ts +30 -0
  26. package/dist/plugin-server/services/subscriber.service.js +90 -0
  27. package/dist/plugin-server/strategies/default-merchant-export-strategy.d.ts +21 -0
  28. package/dist/plugin-server/strategies/default-merchant-export-strategy.js +34 -0
  29. package/dist/plugin-server/types.d.ts +29 -0
  30. package/dist/plugin-server/types.js +2 -0
  31. package/dist/plugin-server/ui/graphql/mutations.ts +23 -0
  32. package/dist/plugin-server/ui/graphql/queries.ts +18 -0
  33. package/dist/plugin-server/ui/graphql/scalars.ts +12 -0
  34. package/dist/plugin-server/ui/pages/FacebookPage.tsx +259 -0
  35. package/dist/plugin-server/ui/pages/GooglePage.tsx +281 -0
  36. package/dist/plugin-server/ui/providers.ts +42 -0
  37. package/dist/plugin-server/ui/routes.ts +18 -0
  38. package/dist/plugin-server/ui/styles/styles.css +3 -0
  39. package/dist/plugin-server/ui/styles/tailwind.css +1297 -0
  40. package/dist/plugin-server/ui/zeus/const.ts +3981 -0
  41. package/dist/plugin-server/ui/zeus/index.ts +18354 -0
  42. package/dist/plugin-server/ui/zeus/typedDocumentNode.ts +30 -0
  43. package/dist/plugin-server/ui.d.ts +2 -0
  44. package/dist/plugin-server/ui.js +15 -0
  45. package/dist/plugin-server/zeus/const.d.ts +6 -0
  46. package/dist/plugin-server/zeus/const.js +3687 -0
  47. package/dist/plugin-server/zeus/index.d.ts +18769 -0
  48. package/dist/plugin-server/zeus/index.js +466 -0
  49. package/dist/plugin-server/zeus/selectors.d.ts +52 -0
  50. package/dist/plugin-server/zeus/selectors.js +51 -0
  51. package/dist/plugin-ui/graphql/mutations.d.ts +29 -0
  52. package/dist/plugin-ui/graphql/mutations.js +17 -0
  53. package/dist/plugin-ui/graphql/queries.d.ts +19 -0
  54. package/dist/plugin-ui/graphql/queries.js +17 -0
  55. package/dist/plugin-ui/graphql/scalars.d.ts +10 -0
  56. package/dist/plugin-ui/graphql/scalars.js +11 -0
  57. package/dist/plugin-ui/index.d.ts +1 -0
  58. package/dist/plugin-ui/index.js +40 -0
  59. package/dist/plugin-ui/locales/en/index.d.ts +8 -0
  60. package/dist/plugin-ui/locales/en/index.js +2 -0
  61. package/dist/plugin-ui/locales/en/merchant.json +7 -0
  62. package/dist/plugin-ui/locales/pl/index.d.ts +8 -0
  63. package/dist/plugin-ui/locales/pl/index.js +2 -0
  64. package/dist/plugin-ui/locales/pl/merchant.json +7 -0
  65. package/dist/plugin-ui/pages/FacebookPage.d.ts +1 -0
  66. package/dist/plugin-ui/pages/FacebookPage.js +130 -0
  67. package/dist/plugin-ui/pages/GooglePage.d.ts +1 -0
  68. package/dist/plugin-ui/pages/GooglePage.js +155 -0
  69. package/dist/plugin-ui/translation-ns.d.ts +1 -0
  70. package/dist/plugin-ui/translation-ns.js +1 -0
  71. package/dist/plugin-ui/tsconfig.json +18 -0
  72. package/dist/plugin-ui/zeus/const.d.ts +6 -0
  73. package/dist/plugin-ui/zeus/const.js +3684 -0
  74. package/dist/plugin-ui/zeus/index.d.ts +18769 -0
  75. package/dist/plugin-ui/zeus/index.js +459 -0
  76. package/dist/plugin-ui/zeus/typedDocumentNode.d.ts +3 -0
  77. package/dist/plugin-ui/zeus/typedDocumentNode.js +9 -0
  78. package/package.json +63 -0
@@ -0,0 +1,219 @@
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
+ var __rest = (this && this.__rest) || function (s, e) {
12
+ var t = {};
13
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14
+ t[p] = s[p];
15
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
16
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18
+ t[p[i]] = s[p[i]];
19
+ }
20
+ return t;
21
+ };
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.FacebookPlatformIntegrationService = void 0;
24
+ const core_1 = require("@deenruv/core");
25
+ const common_1 = require("@nestjs/common");
26
+ const facebook_nodejs_business_sdk_1 = require("facebook-nodejs-business-sdk");
27
+ const platform_integration_settings_entity_js_1 = require("../entities/platform-integration-settings.entity.js");
28
+ const merchant_strategy_service_js_1 = require("./merchant-strategy.service.js");
29
+ const typeorm_1 = require("typeorm");
30
+ let FacebookPlatformIntegrationService = class FacebookPlatformIntegrationService {
31
+ constructor(connection, strategy) {
32
+ this.connection = connection;
33
+ this.strategy = strategy;
34
+ this.logger = new core_1.Logger();
35
+ this.log = (message) => this.logger.log(message, "Merchant Platform Service");
36
+ this.error = (message, err) => this.logger.error(message, err instanceof Error ? err.stack : String(err), "Merchant Platform Service");
37
+ }
38
+ async removeOrphanItems(ctx, items) {
39
+ const { catalog } = await this.withCatalog(ctx);
40
+ const payload = items.map((item) => ({
41
+ retailer_id: item.communicateID,
42
+ method: "DELETE",
43
+ data: {},
44
+ }));
45
+ if (payload.length > 0) {
46
+ await catalog.createBatch([], { requests: payload });
47
+ }
48
+ }
49
+ async getAllProducts(ctx) {
50
+ const { catalog } = await this.withCatalog(ctx);
51
+ let cursor = await catalog.getProducts(["retailer_id", "name"], { limit: 200 }, false);
52
+ const collected = [];
53
+ while (true) {
54
+ collected.push(...cursor.map((p) => p["_data"]));
55
+ if (!cursor.hasNext || !cursor.hasNext())
56
+ break;
57
+ cursor = await cursor.next();
58
+ if (!cursor || cursor.length === 0)
59
+ break;
60
+ }
61
+ return collected
62
+ .filter((item) => item && item.retailer_id)
63
+ .map((item) => ({
64
+ communicateID: item.retailer_id,
65
+ name: item.name,
66
+ }));
67
+ }
68
+ async withCatalog(ctx) {
69
+ const settings = await this.setFacebookSettings(ctx);
70
+ if (!settings) {
71
+ throw new Error("Facebook platform settings not found");
72
+ }
73
+ const { accessToken, catalogId, brand } = settings;
74
+ facebook_nodejs_business_sdk_1.FacebookAdsApi.init(accessToken);
75
+ const catalog = new facebook_nodejs_business_sdk_1.ProductCatalog(catalogId);
76
+ return { catalog, brand };
77
+ }
78
+ async sendBatch(opts) {
79
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
80
+ const { ctx, method, data } = opts;
81
+ try {
82
+ const { catalog, brand } = await this.withCatalog(ctx);
83
+ const requests = await this.prepareFacebookProductPayload({
84
+ ctx,
85
+ method,
86
+ data,
87
+ brand,
88
+ });
89
+ if (!requests || requests.length === 0) {
90
+ return { status: "success", message: "No products to process" };
91
+ }
92
+ const response = await catalog.createBatch([], { requests });
93
+ const hasValidationErrors = Array.isArray((_a = response === null || response === void 0 ? void 0 : response._data) === null || _a === void 0 ? void 0 : _a.validation_status) &&
94
+ response._data.validation_status.length > 0;
95
+ if (hasValidationErrors) {
96
+ this.error(`FB batch ${method} validation errors`);
97
+ return {
98
+ status: "error",
99
+ message: JSON.stringify(response._data.validation_status),
100
+ };
101
+ }
102
+ const items = (_j = (_g = (_e = (_c = (_b = response._data) === null || _b === void 0 ? void 0 : _b.responses) !== null && _c !== void 0 ? _c : (_d = response._data) === null || _d === void 0 ? void 0 : _d.results) !== null && _e !== void 0 ? _e : (_f = response._data) === null || _f === void 0 ? void 0 : _f.handles) !== null && _g !== void 0 ? _g : (_h = response._data) === null || _h === void 0 ? void 0 : _h.data) !== null && _j !== void 0 ? _j : null;
103
+ if (Array.isArray(items)) {
104
+ const errored = items.filter((it) => (it === null || it === void 0 ? void 0 : it.error) ||
105
+ (it === null || it === void 0 ? void 0 : it.error_message) ||
106
+ (it === null || it === void 0 ? void 0 : it.code) >= 400 ||
107
+ (Array.isArray(it === null || it === void 0 ? void 0 : it.errors) && it.errors.length > 0));
108
+ if (errored.length > 0) {
109
+ this.error(`FB batch ${method} per-item errors`, errored);
110
+ return {
111
+ status: "error",
112
+ message: JSON.stringify(errored.slice(0, 3)),
113
+ };
114
+ }
115
+ }
116
+ if (method !== "DELETE") {
117
+ const repo = this.connection.getRepository(ctx, core_1.ProductVariant);
118
+ const items = data.filter((d) => d.variantID);
119
+ const ids = [...new Set(items.map((i) => i.variantID))];
120
+ if (ids.length) {
121
+ const variants = await repo.find({ where: { id: (0, typeorm_1.In)(ids) } });
122
+ const variantMap = new Map(variants.map((v) => [v.id, v]));
123
+ const toDelete = [];
124
+ for (const item of items) {
125
+ const variant = variantMap.get(item.variantID);
126
+ if (!variant)
127
+ continue;
128
+ const newCommId = item.communicateID;
129
+ const prevCommId = (_k = variant.customFields) === null || _k === void 0 ? void 0 : _k.communicateID;
130
+ if (method === "UPDATE" && prevCommId && prevCommId !== newCommId) {
131
+ toDelete.push({
132
+ communicateID: prevCommId,
133
+ variantID: variant.id,
134
+ });
135
+ }
136
+ variant.customFields = Object.assign(Object.assign({}, variant.customFields), { communicateID: newCommId });
137
+ }
138
+ if (toDelete.length)
139
+ await this.sendBatch({ ctx, method: "DELETE", data: toDelete });
140
+ if (variants.length)
141
+ await repo.save(variants, { chunk: 100 });
142
+ }
143
+ }
144
+ this.log(`FB batch ${method} done`);
145
+ return { status: "success", message: "Products processed successfully" };
146
+ }
147
+ catch (e) {
148
+ this.error(`FB batch ${method} failed`);
149
+ return { status: "error", message: e instanceof Error ? e.message : "" };
150
+ }
151
+ }
152
+ async createProduct({ ctx, data, }) {
153
+ return this.sendBatch({ ctx, method: "CREATE", data });
154
+ }
155
+ async updateProduct({ ctx, data, }) {
156
+ return this.sendBatch({ ctx, method: "UPDATE", data });
157
+ }
158
+ async deleteProduct({ ctx, data, }) {
159
+ return this.sendBatch({ ctx, method: "DELETE", data });
160
+ }
161
+ async batchProductsAction({ ctx, products, }) {
162
+ const flatten = products.flat();
163
+ const batchSize = 500;
164
+ for (let i = 0; i < flatten.length; i += batchSize) {
165
+ const batch = flatten.slice(i, i + batchSize);
166
+ await this.sendBatch({ ctx, method: "UPDATE", data: batch });
167
+ }
168
+ return { status: "success", message: "Products processed successfully" };
169
+ }
170
+ async setFacebookSettings(ctx, rawSettings) {
171
+ var _a;
172
+ let settings = rawSettings;
173
+ if (!settings) {
174
+ settings = await this.connection
175
+ .getRepository(ctx, platform_integration_settings_entity_js_1.MerchantPlatformSettingsEntity)
176
+ .findOne({ relations: ["entries"], where: { platform: "facebook" } });
177
+ }
178
+ if (!settings)
179
+ return null;
180
+ const getVal = (key) => { var _a; return (_a = settings.entries.find((e) => e.key === key)) === null || _a === void 0 ? void 0 : _a.value; };
181
+ const autoUpdate = getVal("autoUpdate");
182
+ const credentials = getVal("credentials");
183
+ const merchantId = getVal("merchantId");
184
+ const brand = (_a = getVal("brand")) !== null && _a !== void 0 ? _a : "";
185
+ if (!credentials || !merchantId)
186
+ return null;
187
+ return {
188
+ autoUpdate: String(autoUpdate).toLowerCase() === "true",
189
+ accessToken: credentials,
190
+ catalogId: merchantId,
191
+ brand,
192
+ };
193
+ }
194
+ async prepareFacebookProductPayload({ ctx, method, data, brand, }) {
195
+ if (method === "DELETE") {
196
+ return data.map(({ communicateID }) => ({
197
+ retailer_id: `${communicateID}`,
198
+ method,
199
+ data: {},
200
+ }));
201
+ }
202
+ const products = await this.strategy.prepareFacebookProductPayload(ctx, data);
203
+ return products === null || products === void 0 ? void 0 : products.map((_a) => {
204
+ var _b, _c;
205
+ var { communicateID, variantID } = _a, product = __rest(_a, ["communicateID", "variantID"]);
206
+ return ({
207
+ retailer_id: `${communicateID}`,
208
+ method,
209
+ data: Object.assign(Object.assign({}, product), { brand: (_c = (_b = product.brand) !== null && _b !== void 0 ? _b : brand) !== null && _c !== void 0 ? _c : "" }),
210
+ });
211
+ });
212
+ }
213
+ };
214
+ exports.FacebookPlatformIntegrationService = FacebookPlatformIntegrationService;
215
+ exports.FacebookPlatformIntegrationService = FacebookPlatformIntegrationService = __decorate([
216
+ (0, common_1.Injectable)(),
217
+ __metadata("design:paramtypes", [core_1.TransactionalConnection,
218
+ merchant_strategy_service_js_1.MerchantStrategyService])
219
+ ], FacebookPlatformIntegrationService);
@@ -0,0 +1,63 @@
1
+ import { Product, RequestContext, TransactionalConnection } from "@deenruv/core";
2
+ import { content_v2_1 } from "googleapis";
3
+ import { MerchantPlatformSettingsEntity } from "../entities/platform-integration-settings.entity.js";
4
+ import { BaseData, BaseProductData } from "../types.js";
5
+ import { MerchantStrategyService } from "./merchant-strategy.service.js";
6
+ type OpResult = {
7
+ status: "success";
8
+ } | {
9
+ status: "error";
10
+ error?: unknown;
11
+ };
12
+ export declare class GooglePlatformIntegrationService {
13
+ private readonly connection;
14
+ private readonly strategy;
15
+ private readonly SCOPES;
16
+ private readonly google_content_api_version;
17
+ private readonly logger;
18
+ private log;
19
+ private error;
20
+ private googleContext;
21
+ private googleContextString;
22
+ constructor(connection: TransactionalConnection, strategy: MerchantStrategyService);
23
+ removeOrphanItems(ctx: RequestContext, items: BaseData[]): Promise<void>;
24
+ getAllProducts(ctx: RequestContext): Promise<Array<{
25
+ communicateID: string;
26
+ name?: string;
27
+ }>>;
28
+ private getAuthorization;
29
+ getGoogleProduct({ communicateID }: {
30
+ communicateID: string;
31
+ }): Promise<content_v2_1.Schema$Product | null>;
32
+ insertProduct<T extends BaseData>(opts: {
33
+ ctx: RequestContext;
34
+ data: BaseProductData<T>;
35
+ entity: Product;
36
+ skipCheck?: boolean;
37
+ }): Promise<OpResult>;
38
+ updateProduct(opts: {
39
+ ctx: RequestContext;
40
+ data: BaseProductData<BaseData>;
41
+ entity: Product;
42
+ }): Promise<OpResult>;
43
+ deleteProduct(opts: {
44
+ ctx: RequestContext;
45
+ data: BaseProductData<BaseData>;
46
+ entity: Product;
47
+ }): Promise<OpResult>;
48
+ batchProductsAction(opts: {
49
+ ctx: RequestContext;
50
+ products: BaseProductData<BaseData>[];
51
+ }): Promise<OpResult>;
52
+ private sendBatch;
53
+ private buildPayload;
54
+ private mapInsertProducts;
55
+ private mapUpdateProducts;
56
+ setGoogleSettings(rawSettings?: MerchantPlatformSettingsEntity): Promise<{
57
+ autoUpdate: boolean;
58
+ accountId: string;
59
+ credentials: any;
60
+ brand: string;
61
+ } | null>;
62
+ }
63
+ export {};
@@ -0,0 +1,352 @@
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
+ var __rest = (this && this.__rest) || function (s, e) {
12
+ var t = {};
13
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
14
+ t[p] = s[p];
15
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
16
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
17
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
18
+ t[p[i]] = s[p[i]];
19
+ }
20
+ return t;
21
+ };
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.GooglePlatformIntegrationService = void 0;
24
+ const core_1 = require("@deenruv/core");
25
+ const common_1 = require("@nestjs/common");
26
+ const googleapis_1 = require("googleapis");
27
+ const platform_integration_settings_entity_js_1 = require("../entities/platform-integration-settings.entity.js");
28
+ const merchant_strategy_service_js_1 = require("./merchant-strategy.service.js");
29
+ const typeorm_1 = require("typeorm");
30
+ let GooglePlatformIntegrationService = class GooglePlatformIntegrationService {
31
+ constructor(connection, strategy) {
32
+ this.connection = connection;
33
+ this.strategy = strategy;
34
+ this.SCOPES = ["https://www.googleapis.com/auth/content"];
35
+ this.google_content_api_version = "v2.1";
36
+ this.logger = new core_1.Logger();
37
+ this.log = (message) => this.logger.log(message, "Merchant Platform Service");
38
+ this.error = (message, err) => this.logger.error(message, err instanceof Error ? err.stack : String(err), "Merchant Platform Service");
39
+ this.googleContext = {
40
+ channel: "online",
41
+ contentLanguage: "pl",
42
+ feedLabel: "PL",
43
+ };
44
+ this.googleContextString = (id) => Object.values(this.googleContext).join(":") + `:${id}`;
45
+ }
46
+ async removeOrphanItems(ctx, items) {
47
+ const { accountId, client } = await this.getAuthorization();
48
+ const payload = items.map((item) => ({
49
+ merchantId: accountId,
50
+ productId: this.googleContextString(item.communicateID),
51
+ }));
52
+ if (payload.length > 0) {
53
+ await client.products.custombatch({
54
+ requestBody: {
55
+ entries: payload.map((p) => (Object.assign(Object.assign({}, p), { method: "delete" }))),
56
+ },
57
+ });
58
+ }
59
+ }
60
+ async getAllProducts(ctx) {
61
+ var _a, _b, _c;
62
+ const { accountId, client } = await this.getAuthorization();
63
+ const collected = [];
64
+ let pageToken;
65
+ let page = 0;
66
+ try {
67
+ do {
68
+ const { data } = await client.products.list({
69
+ merchantId: accountId,
70
+ pageToken,
71
+ maxResults: 250,
72
+ });
73
+ const resources = (_a = data.resources) !== null && _a !== void 0 ? _a : [];
74
+ for (const p of resources) {
75
+ if (p.offerId) {
76
+ collected.push({
77
+ communicateID: p.offerId,
78
+ name: (_b = p.title) !== null && _b !== void 0 ? _b : undefined,
79
+ });
80
+ }
81
+ }
82
+ pageToken = (_c = data.nextPageToken) !== null && _c !== void 0 ? _c : undefined;
83
+ page++;
84
+ if (page > 500) {
85
+ this.error("Aborting products pagination: too many pages");
86
+ break;
87
+ }
88
+ } while (pageToken);
89
+ return collected;
90
+ }
91
+ catch (e) {
92
+ this.error("Failed to retrieve products from Google", e);
93
+ return [];
94
+ }
95
+ }
96
+ async getAuthorization() {
97
+ const settings = await this.setGoogleSettings();
98
+ if (!settings)
99
+ throw new Error("Google platform settings not found");
100
+ const { accountId, credentials } = settings;
101
+ const auth = new googleapis_1.google.auth.GoogleAuth({
102
+ credentials,
103
+ scopes: this.SCOPES,
104
+ });
105
+ const client = googleapis_1.google.content({
106
+ version: this.google_content_api_version,
107
+ auth,
108
+ });
109
+ if (!client)
110
+ throw new Error("Content API client not found");
111
+ if (!accountId)
112
+ throw new Error("Merchant ID not found");
113
+ return Object.assign({ client }, settings);
114
+ }
115
+ async getGoogleProduct({ communicateID }) {
116
+ var _a, _b;
117
+ const { accountId, client } = await this.getAuthorization();
118
+ try {
119
+ const response = await client.products.get({
120
+ merchantId: accountId,
121
+ productId: this.googleContextString(communicateID),
122
+ });
123
+ return (_a = response.data) !== null && _a !== void 0 ? _a : null;
124
+ }
125
+ catch (e) {
126
+ const err = e;
127
+ if (((_b = err.response) === null || _b === void 0 ? void 0 : _b.status) === 404)
128
+ return null;
129
+ throw e;
130
+ }
131
+ }
132
+ async insertProduct(opts) {
133
+ return this.sendBatch(Object.assign(Object.assign({}, opts), { method: "insert" }));
134
+ }
135
+ async updateProduct(opts) {
136
+ return this.sendBatch(Object.assign(Object.assign({}, opts), { method: "update" }));
137
+ }
138
+ async deleteProduct(opts) {
139
+ return this.sendBatch(Object.assign(Object.assign({}, opts), { method: "delete" }));
140
+ }
141
+ async batchProductsAction(opts) {
142
+ var _a, _b, _c, _d, _e;
143
+ const { ctx, products } = opts;
144
+ try {
145
+ const { accountId, brand, client } = await this.getAuthorization();
146
+ const allPayloads = await Promise.all(products.map((p) => this.buildPayload(ctx, p))).then((arr) => arr.flat());
147
+ if (allPayloads.length === 0)
148
+ return { status: "success" };
149
+ const insertProducts = this.mapInsertProducts(allPayloads, brand);
150
+ const entries = insertProducts.map((product, i) => ({
151
+ batchId: i + 1,
152
+ merchantId: accountId,
153
+ method: "insert",
154
+ product,
155
+ }));
156
+ const resp = await client.products.custombatch({
157
+ requestBody: { entries },
158
+ });
159
+ if (resp.status !== 200)
160
+ throw new Error("Batch insert failed");
161
+ const hasErrors = (_b = (_a = resp.data) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.some((e) => e.errors);
162
+ if (hasErrors)
163
+ throw new Error("Per-item insert errors");
164
+ this.log(`Batch inserted ${(_e = (_d = (_c = resp.data) === null || _c === void 0 ? void 0 : _c.entries) === null || _d === void 0 ? void 0 : _d.length) !== null && _e !== void 0 ? _e : insertProducts.length} product(s)`);
165
+ for (const product of products) {
166
+ for (const item of product) {
167
+ await this.connection
168
+ .getRepository(ctx, core_1.ProductVariant)
169
+ .update({ id: item.variantID }, { customFields: { communicateID: item.communicateID } });
170
+ }
171
+ }
172
+ return { status: "success" };
173
+ }
174
+ catch (e) {
175
+ this.error("Batch products action failed", e);
176
+ return { status: "error", error: e };
177
+ }
178
+ }
179
+ async sendBatch(opts) {
180
+ var _a, _b, _c, _d, _e, _f;
181
+ const { ctx, method, data, skipCheck } = opts;
182
+ try {
183
+ const basePayload = await this.buildPayload(ctx, data);
184
+ if (basePayload.length === 0)
185
+ return { status: "success" };
186
+ const { accountId, brand, client } = await this.getAuthorization();
187
+ let working = basePayload;
188
+ if (method === "insert" && !skipCheck) {
189
+ const filtered = [];
190
+ for (const item of working) {
191
+ if (!item.communicateID)
192
+ continue;
193
+ try {
194
+ const exists = await this.getGoogleProduct({
195
+ communicateID: item.communicateID,
196
+ });
197
+ if (!exists)
198
+ filtered.push(item);
199
+ }
200
+ catch (e) {
201
+ this.log(`Lookup failed for ${item.communicateID}, attempting insert. ${e}`);
202
+ filtered.push(item);
203
+ }
204
+ }
205
+ working = filtered;
206
+ if (working.length === 0)
207
+ return { status: "success" };
208
+ }
209
+ let entries = [];
210
+ if (method === "insert") {
211
+ const products = this.mapInsertProducts(working, brand);
212
+ entries = products.map((product, i) => ({
213
+ batchId: i + 1,
214
+ merchantId: accountId,
215
+ method,
216
+ product,
217
+ }));
218
+ }
219
+ else if (method === "update") {
220
+ const products = this.mapUpdateProducts(working, brand);
221
+ if (products.length === 0)
222
+ return { status: "success" };
223
+ entries = products.map(({ communicateID, product }, i) => ({
224
+ batchId: i + 1,
225
+ merchantId: accountId,
226
+ method,
227
+ productId: communicateID,
228
+ product,
229
+ }));
230
+ }
231
+ else if (method === "delete") {
232
+ entries = working
233
+ .filter((p) => !!p.communicateID)
234
+ .map((p, i) => ({
235
+ batchId: i + 1,
236
+ merchantId: accountId,
237
+ method,
238
+ productId: this.googleContextString(p.communicateID),
239
+ }));
240
+ }
241
+ if (entries.length === 0)
242
+ return { status: "success" };
243
+ const resp = await client.products.custombatch({
244
+ requestBody: { entries },
245
+ });
246
+ if (resp.status !== 200)
247
+ throw new Error(`Batch ${method} HTTP status ${resp.status}`);
248
+ const perItemErrors = (_b = (_a = resp.data) === null || _a === void 0 ? void 0 : _a.entries) === null || _b === void 0 ? void 0 : _b.filter((e) => e.errors);
249
+ if (perItemErrors && perItemErrors.length > 0) {
250
+ this.error(`Google batch ${method} per-item errors`);
251
+ return { status: "error", error: perItemErrors };
252
+ }
253
+ if (method !== "delete") {
254
+ const repo = this.connection.getRepository(ctx, core_1.ProductVariant);
255
+ const items = data.filter((d) => d.variantID);
256
+ const ids = [...new Set(items.map((i) => i.variantID))];
257
+ if (ids.length) {
258
+ const variants = await repo.find({ where: { id: (0, typeorm_1.In)(ids) } });
259
+ const variantMap = new Map(variants.map((v) => [v.id, v]));
260
+ const toDelete = [];
261
+ for (const item of items) {
262
+ const variant = variantMap.get(item.variantID);
263
+ if (!variant)
264
+ continue;
265
+ const newCommId = item.communicateID;
266
+ const prevCommId = (_c = variant.customFields) === null || _c === void 0 ? void 0 : _c.communicateID;
267
+ if (method === "update" && prevCommId && prevCommId !== newCommId) {
268
+ toDelete.push({
269
+ communicateID: prevCommId,
270
+ variantID: variant.id,
271
+ });
272
+ }
273
+ variant.customFields = Object.assign(Object.assign({}, variant.customFields), { communicateID: newCommId });
274
+ }
275
+ if (toDelete.length)
276
+ await this.sendBatch({ ctx, method: "delete", data: toDelete });
277
+ if (variants.length)
278
+ await repo.save(variants, { chunk: 100 });
279
+ }
280
+ }
281
+ this.log(`Google batch ${method} done (${(_f = (_e = (_d = resp.data) === null || _d === void 0 ? void 0 : _d.entries) === null || _e === void 0 ? void 0 : _e.length) !== null && _f !== void 0 ? _f : entries.length} items)`);
282
+ return { status: "success" };
283
+ }
284
+ catch (e) {
285
+ this.error(`Google batch ${method} failed`, e);
286
+ return { status: "error", error: e };
287
+ }
288
+ }
289
+ async buildPayload(ctx, data) {
290
+ const payload = await this.strategy.prepareGoogleProductPayload(ctx, data);
291
+ return (payload !== null && payload !== void 0 ? payload : []).filter((p) => !!p && !!p.communicateID);
292
+ }
293
+ mapInsertProducts(payload, brand) {
294
+ return payload.map((_a) => {
295
+ var { communicateID, variantID } = _a, rest = __rest(_a, ["communicateID", "variantID"]);
296
+ return (Object.assign(Object.assign(Object.assign({ offerId: communicateID.toString() }, rest), this.googleContext), { brand }));
297
+ });
298
+ }
299
+ mapUpdateProducts(payload, brand) {
300
+ return payload
301
+ .filter((p) => !!p.communicateID)
302
+ .map((_a) => {
303
+ var { communicateID, variantID } = _a, rest = __rest(_a, ["communicateID", "variantID"]);
304
+ delete rest.offerId;
305
+ delete rest.feedLabel;
306
+ delete rest.contentLanguage;
307
+ delete rest.channel;
308
+ const product = Object.assign(Object.assign({}, rest), { brand });
309
+ return {
310
+ communicateID: this.googleContextString(communicateID),
311
+ product,
312
+ };
313
+ });
314
+ }
315
+ async setGoogleSettings(rawSettings) {
316
+ var _a, _b;
317
+ let settings = rawSettings;
318
+ if (!settings) {
319
+ settings = await this.connection
320
+ .getRepository(core_1.RequestContext.empty(), platform_integration_settings_entity_js_1.MerchantPlatformSettingsEntity)
321
+ .findOne({ relations: ["entries"], where: { platform: "google" } });
322
+ }
323
+ if (!settings)
324
+ return null;
325
+ const getVal = (k) => { var _a; return (_a = settings.entries.find((e) => e.key === k)) === null || _a === void 0 ? void 0 : _a.value; };
326
+ const autoUpdate = getVal("autoUpdate");
327
+ const accountId = getVal("merchantId");
328
+ const brand = (_a = getVal("brand")) !== null && _a !== void 0 ? _a : "";
329
+ let credentials = null;
330
+ try {
331
+ credentials = JSON.parse((_b = getVal("credentials")) !== null && _b !== void 0 ? _b : "null");
332
+ }
333
+ catch (_c) {
334
+ this.log("Error parsing credentials");
335
+ return null;
336
+ }
337
+ if (!accountId || !credentials || !brand)
338
+ return null;
339
+ return {
340
+ autoUpdate: String(autoUpdate).toLowerCase() === "true",
341
+ accountId,
342
+ credentials,
343
+ brand,
344
+ };
345
+ }
346
+ };
347
+ exports.GooglePlatformIntegrationService = GooglePlatformIntegrationService;
348
+ exports.GooglePlatformIntegrationService = GooglePlatformIntegrationService = __decorate([
349
+ (0, common_1.Injectable)(),
350
+ __metadata("design:paramtypes", [core_1.TransactionalConnection,
351
+ merchant_strategy_service_js_1.MerchantStrategyService])
352
+ ], GooglePlatformIntegrationService);
@@ -0,0 +1,10 @@
1
+ import { BaseData, BaseProductData, MerchantExportStrategy, MerchantPluginOptions } from "../types.js";
2
+ import { Product, RequestContext } from "@deenruv/core";
3
+ export declare class MerchantStrategyService {
4
+ private readonly options;
5
+ strategy: MerchantExportStrategy<BaseProductData<BaseData>>;
6
+ constructor(options: MerchantPluginOptions);
7
+ getBaseData(ctx: RequestContext, product: Product): Promise<BaseProductData<BaseData> | undefined>;
8
+ prepareGoogleProductPayload(ctx: RequestContext, product: BaseProductData<BaseData>): Promise<import("../types.js").GoogleProduct[] | undefined>;
9
+ prepareFacebookProductPayload(ctx: RequestContext, product: BaseProductData<BaseData>): Promise<import("../types.js").FacebookProduct[] | undefined>;
10
+ }