@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.
- package/LICENSE +23 -0
- package/README.md +55 -0
- package/dist/plugin-server/api/platform-integration-admin-resolver.d.ts +26 -0
- package/dist/plugin-server/api/platform-integration-admin-resolver.js +103 -0
- package/dist/plugin-server/constants.d.ts +1 -0
- package/dist/plugin-server/constants.js +4 -0
- package/dist/plugin-server/e2e/plugin.e2e.test.d.ts +1 -0
- package/dist/plugin-server/e2e/plugin.e2e.test.js +36 -0
- package/dist/plugin-server/entities/platform-integration-setting.entity.d.ts +10 -0
- package/dist/plugin-server/entities/platform-integration-setting.entity.js +37 -0
- package/dist/plugin-server/entities/platform-integration-settings.entity.d.ts +9 -0
- package/dist/plugin-server/entities/platform-integration-settings.entity.js +33 -0
- package/dist/plugin-server/extensions/api-extensions.d.ts +2 -0
- package/dist/plugin-server/extensions/api-extensions.js +49 -0
- package/dist/plugin-server/index.d.ts +14 -0
- package/dist/plugin-server/index.js +100 -0
- package/dist/plugin-server/services/facebook-platform-integration.service.d.ts +51 -0
- package/dist/plugin-server/services/facebook-platform-integration.service.js +219 -0
- package/dist/plugin-server/services/google-platform-integration.service.d.ts +63 -0
- package/dist/plugin-server/services/google-platform-integration.service.js +352 -0
- package/dist/plugin-server/services/merchant-strategy.service.d.ts +10 -0
- package/dist/plugin-server/services/merchant-strategy.service.js +39 -0
- package/dist/plugin-server/services/platform-integration.service.d.ts +43 -0
- package/dist/plugin-server/services/platform-integration.service.js +282 -0
- package/dist/plugin-server/services/subscriber.service.d.ts +30 -0
- package/dist/plugin-server/services/subscriber.service.js +90 -0
- package/dist/plugin-server/strategies/default-merchant-export-strategy.d.ts +21 -0
- package/dist/plugin-server/strategies/default-merchant-export-strategy.js +34 -0
- package/dist/plugin-server/types.d.ts +29 -0
- package/dist/plugin-server/types.js +2 -0
- package/dist/plugin-server/ui/graphql/mutations.ts +23 -0
- package/dist/plugin-server/ui/graphql/queries.ts +18 -0
- package/dist/plugin-server/ui/graphql/scalars.ts +12 -0
- package/dist/plugin-server/ui/pages/FacebookPage.tsx +259 -0
- package/dist/plugin-server/ui/pages/GooglePage.tsx +281 -0
- package/dist/plugin-server/ui/providers.ts +42 -0
- package/dist/plugin-server/ui/routes.ts +18 -0
- package/dist/plugin-server/ui/styles/styles.css +3 -0
- package/dist/plugin-server/ui/styles/tailwind.css +1297 -0
- package/dist/plugin-server/ui/zeus/const.ts +3981 -0
- package/dist/plugin-server/ui/zeus/index.ts +18354 -0
- package/dist/plugin-server/ui/zeus/typedDocumentNode.ts +30 -0
- package/dist/plugin-server/ui.d.ts +2 -0
- package/dist/plugin-server/ui.js +15 -0
- package/dist/plugin-server/zeus/const.d.ts +6 -0
- package/dist/plugin-server/zeus/const.js +3687 -0
- package/dist/plugin-server/zeus/index.d.ts +18769 -0
- package/dist/plugin-server/zeus/index.js +466 -0
- package/dist/plugin-server/zeus/selectors.d.ts +52 -0
- package/dist/plugin-server/zeus/selectors.js +51 -0
- package/dist/plugin-ui/graphql/mutations.d.ts +29 -0
- package/dist/plugin-ui/graphql/mutations.js +17 -0
- package/dist/plugin-ui/graphql/queries.d.ts +19 -0
- package/dist/plugin-ui/graphql/queries.js +17 -0
- package/dist/plugin-ui/graphql/scalars.d.ts +10 -0
- package/dist/plugin-ui/graphql/scalars.js +11 -0
- package/dist/plugin-ui/index.d.ts +1 -0
- package/dist/plugin-ui/index.js +40 -0
- package/dist/plugin-ui/locales/en/index.d.ts +8 -0
- package/dist/plugin-ui/locales/en/index.js +2 -0
- package/dist/plugin-ui/locales/en/merchant.json +7 -0
- package/dist/plugin-ui/locales/pl/index.d.ts +8 -0
- package/dist/plugin-ui/locales/pl/index.js +2 -0
- package/dist/plugin-ui/locales/pl/merchant.json +7 -0
- package/dist/plugin-ui/pages/FacebookPage.d.ts +1 -0
- package/dist/plugin-ui/pages/FacebookPage.js +130 -0
- package/dist/plugin-ui/pages/GooglePage.d.ts +1 -0
- package/dist/plugin-ui/pages/GooglePage.js +155 -0
- package/dist/plugin-ui/translation-ns.d.ts +1 -0
- package/dist/plugin-ui/translation-ns.js +1 -0
- package/dist/plugin-ui/tsconfig.json +18 -0
- package/dist/plugin-ui/zeus/const.d.ts +6 -0
- package/dist/plugin-ui/zeus/const.js +3684 -0
- package/dist/plugin-ui/zeus/index.d.ts +18769 -0
- package/dist/plugin-ui/zeus/index.js +459 -0
- package/dist/plugin-ui/zeus/typedDocumentNode.d.ts +3 -0
- package/dist/plugin-ui/zeus/typedDocumentNode.js +9 -0
- package/package.json +63 -0
|
@@ -0,0 +1,39 @@
|
|
|
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 __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.MerchantStrategyService = void 0;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
const constants_js_1 = require("../constants.js");
|
|
18
|
+
const default_merchant_export_strategy_js_1 = require("../strategies/default-merchant-export-strategy.js");
|
|
19
|
+
let MerchantStrategyService = class MerchantStrategyService {
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.options = options;
|
|
22
|
+
this.strategy = (options === null || options === void 0 ? void 0 : options.strategy) || new default_merchant_export_strategy_js_1.DefaultMerchantExportStrategy();
|
|
23
|
+
}
|
|
24
|
+
async getBaseData(ctx, product) {
|
|
25
|
+
return this.strategy.getBaseData(ctx, product);
|
|
26
|
+
}
|
|
27
|
+
async prepareGoogleProductPayload(ctx, product) {
|
|
28
|
+
return this.strategy.prepareGoogleProductPayload(ctx, product);
|
|
29
|
+
}
|
|
30
|
+
async prepareFacebookProductPayload(ctx, product) {
|
|
31
|
+
return this.strategy.prepareFacebookProductPayload(ctx, product);
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
exports.MerchantStrategyService = MerchantStrategyService;
|
|
35
|
+
exports.MerchantStrategyService = MerchantStrategyService = __decorate([
|
|
36
|
+
(0, common_1.Injectable)(),
|
|
37
|
+
__param(0, (0, common_1.Inject)(constants_js_1.MERCHANT_PLUGIN_OPTIONS)),
|
|
38
|
+
__metadata("design:paramtypes", [Object])
|
|
39
|
+
], MerchantStrategyService);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { JobQueue, JobQueueService, ProductService, RequestContext, TransactionalConnection } from "@deenruv/core";
|
|
2
|
+
import { OnModuleInit } from "@nestjs/common";
|
|
3
|
+
import { MerchantPlatformSettingsEntity } from "../entities/platform-integration-settings.entity.js";
|
|
4
|
+
import { FacebookPlatformIntegrationService } from "./facebook-platform-integration.service.js";
|
|
5
|
+
import { GooglePlatformIntegrationService } from "./google-platform-integration.service.js";
|
|
6
|
+
import { MerchantStrategyService } from "./merchant-strategy.service.js";
|
|
7
|
+
type JOB_PAYLOAD = {
|
|
8
|
+
platform: string;
|
|
9
|
+
action: "SEND_ALL_PRODUCTS";
|
|
10
|
+
};
|
|
11
|
+
export declare class PlatformIntegrationService implements OnModuleInit {
|
|
12
|
+
private readonly connection;
|
|
13
|
+
private readonly jobQueueService;
|
|
14
|
+
private readonly googleService;
|
|
15
|
+
private readonly facebookService;
|
|
16
|
+
private readonly productService;
|
|
17
|
+
private readonly strategy;
|
|
18
|
+
MerchantPlatformQueue: JobQueue<{
|
|
19
|
+
payload: JOB_PAYLOAD;
|
|
20
|
+
worker: number;
|
|
21
|
+
}>;
|
|
22
|
+
OrphanItemsQueue: JobQueue<{
|
|
23
|
+
platform: string;
|
|
24
|
+
}>;
|
|
25
|
+
private readonly logger;
|
|
26
|
+
private log;
|
|
27
|
+
constructor(connection: TransactionalConnection, jobQueueService: JobQueueService, googleService: GooglePlatformIntegrationService, facebookService: FacebookPlatformIntegrationService, productService: ProductService, strategy: MerchantStrategyService);
|
|
28
|
+
removeOrphanItems(ctx: RequestContext, platform: string): Promise<boolean>;
|
|
29
|
+
fetchProducts(input: {
|
|
30
|
+
ctx: RequestContext;
|
|
31
|
+
worker: number;
|
|
32
|
+
}, callback?: (progress: number) => void): AsyncGenerator<import("../types.js").BaseProductData<import("../types.js").BaseData>, void, unknown>;
|
|
33
|
+
onModuleInit(): Promise<void>;
|
|
34
|
+
createContext(): Promise<RequestContext>;
|
|
35
|
+
savePlatformIntegrationSettings(ctx: RequestContext, settings: MerchantPlatformSettingsEntity): Promise<MerchantPlatformSettingsEntity | undefined>;
|
|
36
|
+
getBaseSettings(ctx: RequestContext, platform: string): Promise<MerchantPlatformSettingsEntity | null>;
|
|
37
|
+
getPlatformAutoUpdateSettings(ctx: RequestContext): Promise<{
|
|
38
|
+
googleAutoUpdate: boolean;
|
|
39
|
+
facebookAutoUpdate: boolean;
|
|
40
|
+
}>;
|
|
41
|
+
private lookup;
|
|
42
|
+
}
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,282 @@
|
|
|
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 __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
12
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
13
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
14
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
15
|
+
return i = {}, verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
16
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
17
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
18
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
19
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
20
|
+
function fulfill(value) { resume("next", value); }
|
|
21
|
+
function reject(value) { resume("throw", value); }
|
|
22
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
23
|
+
};
|
|
24
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
25
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
26
|
+
var m = o[Symbol.asyncIterator], i;
|
|
27
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
28
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
29
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
30
|
+
};
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.PlatformIntegrationService = void 0;
|
|
33
|
+
const admin_types_1 = require("@deenruv/admin-types");
|
|
34
|
+
const core_1 = require("@deenruv/core");
|
|
35
|
+
const common_1 = require("@nestjs/common");
|
|
36
|
+
const platform_integration_settings_entity_js_1 = require("../entities/platform-integration-settings.entity.js");
|
|
37
|
+
const facebook_platform_integration_service_js_1 = require("./facebook-platform-integration.service.js");
|
|
38
|
+
const google_platform_integration_service_js_1 = require("./google-platform-integration.service.js");
|
|
39
|
+
const merchant_strategy_service_js_1 = require("./merchant-strategy.service.js");
|
|
40
|
+
const BATCH_SIZE = 200;
|
|
41
|
+
const WORKER_THRESHOLD = 5000;
|
|
42
|
+
let PlatformIntegrationService = class PlatformIntegrationService {
|
|
43
|
+
constructor(connection, jobQueueService, googleService, facebookService, productService, strategy) {
|
|
44
|
+
this.connection = connection;
|
|
45
|
+
this.jobQueueService = jobQueueService;
|
|
46
|
+
this.googleService = googleService;
|
|
47
|
+
this.facebookService = facebookService;
|
|
48
|
+
this.productService = productService;
|
|
49
|
+
this.strategy = strategy;
|
|
50
|
+
this.logger = new core_1.Logger();
|
|
51
|
+
this.log = (message) => this.logger.log(message, "Merchant Platform Service");
|
|
52
|
+
}
|
|
53
|
+
async removeOrphanItems(ctx, platform) {
|
|
54
|
+
if (["facebook", "google"].includes(platform)) {
|
|
55
|
+
await this.OrphanItemsQueue.add({ platform });
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
fetchProducts(input, callback) {
|
|
61
|
+
return __asyncGenerator(this, arguments, function* fetchProducts_1() {
|
|
62
|
+
const { ctx, worker } = input;
|
|
63
|
+
const totalToFetch = (worker + 1) * WORKER_THRESHOLD;
|
|
64
|
+
const start = worker * WORKER_THRESHOLD;
|
|
65
|
+
for (let i = start; i < totalToFetch; i += BATCH_SIZE) {
|
|
66
|
+
const { items } = yield __await(this.productService.findAll(ctx, {
|
|
67
|
+
take: BATCH_SIZE,
|
|
68
|
+
skip: i,
|
|
69
|
+
}));
|
|
70
|
+
for (const product of items) {
|
|
71
|
+
const baseProduct = yield __await(this.strategy.getBaseData(ctx, product));
|
|
72
|
+
if (callback) {
|
|
73
|
+
const progress = Math.floor((i / totalToFetch) * 100);
|
|
74
|
+
callback(progress);
|
|
75
|
+
}
|
|
76
|
+
if (!baseProduct)
|
|
77
|
+
continue;
|
|
78
|
+
yield yield __await(baseProduct);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
async onModuleInit() {
|
|
84
|
+
this.MerchantPlatformQueue = await this.jobQueueService.createQueue({
|
|
85
|
+
name: "MerchantPlatformEventQueue",
|
|
86
|
+
process: async (job) => {
|
|
87
|
+
var _a, e_1, _b, _c;
|
|
88
|
+
try {
|
|
89
|
+
const { payload: { action, platform }, worker, } = job.data;
|
|
90
|
+
if (action === "SEND_ALL_PRODUCTS") {
|
|
91
|
+
const ctx = await this.createContext();
|
|
92
|
+
if (!ctx)
|
|
93
|
+
return { status: "CONTEXT_ERROR" };
|
|
94
|
+
const products = [];
|
|
95
|
+
try {
|
|
96
|
+
for (var _d = true, _e = __asyncValues(this.fetchProducts({ ctx, worker }, (progress) => {
|
|
97
|
+
if (job.state === admin_types_1.JobState.CANCELLED) {
|
|
98
|
+
throw new Error("Job was cancelled");
|
|
99
|
+
}
|
|
100
|
+
else
|
|
101
|
+
job.setProgress(progress);
|
|
102
|
+
})), _f; _f = await _e.next(), _a = _f.done, !_a; _d = true) {
|
|
103
|
+
_c = _f.value;
|
|
104
|
+
_d = false;
|
|
105
|
+
const product = _c;
|
|
106
|
+
products.push(product);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
110
|
+
finally {
|
|
111
|
+
try {
|
|
112
|
+
if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
|
|
113
|
+
}
|
|
114
|
+
finally { if (e_1) throw e_1.error; }
|
|
115
|
+
}
|
|
116
|
+
let googleResponse = false;
|
|
117
|
+
let facebookResponse = false;
|
|
118
|
+
if (platform === "google") {
|
|
119
|
+
const response = await this.googleService.batchProductsAction({
|
|
120
|
+
ctx,
|
|
121
|
+
products,
|
|
122
|
+
});
|
|
123
|
+
if (response.status === "success") {
|
|
124
|
+
this.log("Products sent to google");
|
|
125
|
+
googleResponse = true;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
this.log("Error sending products to google");
|
|
129
|
+
googleResponse = false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (platform === "facebook") {
|
|
133
|
+
const response = await this.facebookService.batchProductsAction({
|
|
134
|
+
ctx,
|
|
135
|
+
products,
|
|
136
|
+
});
|
|
137
|
+
if (response.status === "success") {
|
|
138
|
+
this.log("Products sent to facebook");
|
|
139
|
+
facebookResponse = true;
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
this.log("Error sending products to facebook");
|
|
143
|
+
facebookResponse = false;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { status: "SUCCESS", facebookResponse, googleResponse };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
const status = e instanceof Error ? e.message : "Unknown error";
|
|
151
|
+
this.log(`Error processing job: ${status}`);
|
|
152
|
+
throw new Error(status);
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
this.OrphanItemsQueue = await this.jobQueueService.createQueue({
|
|
157
|
+
name: "MerchantPlatformOrphanItemsQueue",
|
|
158
|
+
process: async (job) => {
|
|
159
|
+
var _a, e_2, _b, _c;
|
|
160
|
+
const { platform } = job.data;
|
|
161
|
+
const ctx = await this.createContext();
|
|
162
|
+
const map = {
|
|
163
|
+
google: this.googleService,
|
|
164
|
+
facebook: this.facebookService,
|
|
165
|
+
};
|
|
166
|
+
const service = map[platform];
|
|
167
|
+
if (!service)
|
|
168
|
+
throw new Error("Unknown platform");
|
|
169
|
+
const results = await service.getAllProducts(ctx);
|
|
170
|
+
const products = [];
|
|
171
|
+
try {
|
|
172
|
+
for (var _d = true, _e = __asyncValues(this.fetchProducts({ ctx, worker: 0 }, (progress) => {
|
|
173
|
+
if (job.state === admin_types_1.JobState.CANCELLED) {
|
|
174
|
+
throw new Error("Job was cancelled");
|
|
175
|
+
}
|
|
176
|
+
else
|
|
177
|
+
job.setProgress(progress);
|
|
178
|
+
})), _f; _f = await _e.next(), _a = _f.done, !_a; _d = true) {
|
|
179
|
+
_c = _f.value;
|
|
180
|
+
_d = false;
|
|
181
|
+
const product = _c;
|
|
182
|
+
products.push(...product);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (e_2_1) { e_2 = { error: e_2_1 }; }
|
|
186
|
+
finally {
|
|
187
|
+
try {
|
|
188
|
+
if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
|
|
189
|
+
}
|
|
190
|
+
finally { if (e_2) throw e_2.error; }
|
|
191
|
+
}
|
|
192
|
+
const missingProducts = products.filter((product) => !results.find((result) => result.communicateID === product.communicateID));
|
|
193
|
+
if (missingProducts.length > 0) {
|
|
194
|
+
this.log(`Found ${missingProducts.length} orphan items for platform ${platform}`);
|
|
195
|
+
await service.removeOrphanItems(ctx, missingProducts);
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
this.log(`No orphan items found for platform ${platform}`);
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async createContext() {
|
|
204
|
+
const channel = await this.connection.rawConnection
|
|
205
|
+
.getRepository(core_1.Channel)
|
|
206
|
+
.findOne({
|
|
207
|
+
where: { token: "pl-channel" }, // TODO: We should take default channel code, and allow to map other channels
|
|
208
|
+
relations: { defaultTaxZone: true },
|
|
209
|
+
});
|
|
210
|
+
if (!channel)
|
|
211
|
+
throw new Error("Cannot create context, default channel not found");
|
|
212
|
+
return new core_1.RequestContext({
|
|
213
|
+
apiType: "admin",
|
|
214
|
+
channel,
|
|
215
|
+
isAuthorized: true,
|
|
216
|
+
authorizedAsOwnerOnly: true,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async savePlatformIntegrationSettings(ctx, settings) {
|
|
220
|
+
const repository = this.connection.getRepository(ctx, platform_integration_settings_entity_js_1.MerchantPlatformSettingsEntity);
|
|
221
|
+
const existing = await repository.findOne({
|
|
222
|
+
where: { platform: settings.platform },
|
|
223
|
+
});
|
|
224
|
+
if (existing)
|
|
225
|
+
await repository.delete(existing.id);
|
|
226
|
+
let response = await repository.save(settings);
|
|
227
|
+
if (!response.id)
|
|
228
|
+
return;
|
|
229
|
+
const [isFirstSync, isAutoUpdate] = [
|
|
230
|
+
this.lookup(settings, "firstSync") === "true",
|
|
231
|
+
this.lookup(settings, "autoUpdate") === "true",
|
|
232
|
+
];
|
|
233
|
+
if (isFirstSync && isAutoUpdate) {
|
|
234
|
+
const products = await this.productService.findAll(ctx, {
|
|
235
|
+
take: 1,
|
|
236
|
+
skip: 0,
|
|
237
|
+
});
|
|
238
|
+
const total = products.totalItems;
|
|
239
|
+
const workers = Math.ceil(total / WORKER_THRESHOLD);
|
|
240
|
+
for (let worker = 0; worker < workers; worker++) {
|
|
241
|
+
await this.MerchantPlatformQueue.add({
|
|
242
|
+
worker,
|
|
243
|
+
payload: {
|
|
244
|
+
platform: response.platform,
|
|
245
|
+
action: "SEND_ALL_PRODUCTS",
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
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) }));
|
|
250
|
+
}
|
|
251
|
+
return response;
|
|
252
|
+
}
|
|
253
|
+
async getBaseSettings(ctx, platform) {
|
|
254
|
+
return this.connection
|
|
255
|
+
.getRepository(ctx, platform_integration_settings_entity_js_1.MerchantPlatformSettingsEntity)
|
|
256
|
+
.findOne({ relations: ["entries"], where: { platform } });
|
|
257
|
+
}
|
|
258
|
+
async getPlatformAutoUpdateSettings(ctx) {
|
|
259
|
+
const [googlePlatformSettings, facebookPlatformSettings] = await Promise.all([
|
|
260
|
+
this.getBaseSettings(ctx, "google"),
|
|
261
|
+
this.getBaseSettings(ctx, "facebook"),
|
|
262
|
+
]);
|
|
263
|
+
return {
|
|
264
|
+
googleAutoUpdate: this.lookup(googlePlatformSettings, "autoUpdate") === "true",
|
|
265
|
+
facebookAutoUpdate: this.lookup(facebookPlatformSettings, "autoUpdate") === "true",
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
lookup(settings, key) {
|
|
269
|
+
var _a;
|
|
270
|
+
return (_a = settings === null || settings === void 0 ? void 0 : settings.entries.find((entry) => entry.key === key)) === null || _a === void 0 ? void 0 : _a.value;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
exports.PlatformIntegrationService = PlatformIntegrationService;
|
|
274
|
+
exports.PlatformIntegrationService = PlatformIntegrationService = __decorate([
|
|
275
|
+
(0, common_1.Injectable)(),
|
|
276
|
+
__metadata("design:paramtypes", [core_1.TransactionalConnection,
|
|
277
|
+
core_1.JobQueueService,
|
|
278
|
+
google_platform_integration_service_js_1.GooglePlatformIntegrationService,
|
|
279
|
+
facebook_platform_integration_service_js_1.FacebookPlatformIntegrationService,
|
|
280
|
+
core_1.ProductService,
|
|
281
|
+
merchant_strategy_service_js_1.MerchantStrategyService])
|
|
282
|
+
], PlatformIntegrationService);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CreateProductInput, CreateProductVariantInput, UpdateProductInput, UpdateProductVariantInput } from "@deenruv/common/lib/generated-types.js";
|
|
2
|
+
import { EventBus, ID, Product, ProductService, RequestContext } from "@deenruv/core";
|
|
3
|
+
import { OnApplicationBootstrap } from "@nestjs/common";
|
|
4
|
+
import { MerchantPluginOptions } from "../types.js";
|
|
5
|
+
import { FacebookPlatformIntegrationService } from "./facebook-platform-integration.service.js";
|
|
6
|
+
import { GooglePlatformIntegrationService } from "./google-platform-integration.service.js";
|
|
7
|
+
import { MerchantStrategyService } from "./merchant-strategy.service.js";
|
|
8
|
+
import { PlatformIntegrationService } from "./platform-integration.service.js";
|
|
9
|
+
type ProductInputTypes = CreateProductInput | UpdateProductInput | ID;
|
|
10
|
+
type ProductVariantInputTypes = CreateProductVariantInput[] | UpdateProductVariantInput[] | ID | ID[];
|
|
11
|
+
export declare class SubscriberService implements OnApplicationBootstrap {
|
|
12
|
+
private readonly options;
|
|
13
|
+
private readonly eventBus;
|
|
14
|
+
private readonly integrationService;
|
|
15
|
+
private readonly googleService;
|
|
16
|
+
private readonly facebookService;
|
|
17
|
+
private readonly strategy;
|
|
18
|
+
private readonly productService;
|
|
19
|
+
constructor(options: MerchantPluginOptions, eventBus: EventBus, integrationService: PlatformIntegrationService, googleService: GooglePlatformIntegrationService, facebookService: FacebookPlatformIntegrationService, strategy: MerchantStrategyService, productService: ProductService);
|
|
20
|
+
onApplicationBootstrap(): Promise<void>;
|
|
21
|
+
event({ ctx, entity, type, input, }: {
|
|
22
|
+
ctx: RequestContext;
|
|
23
|
+
entity: Product;
|
|
24
|
+
type: "created" | "updated" | "deleted";
|
|
25
|
+
input?: ProductInputTypes | ProductVariantInputTypes;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
private isValidEventData;
|
|
28
|
+
private getOperationsForType;
|
|
29
|
+
}
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,90 @@
|
|
|
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 __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.SubscriberService = void 0;
|
|
16
|
+
const core_1 = require("@deenruv/core");
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const constants_js_1 = require("../constants.js");
|
|
19
|
+
const facebook_platform_integration_service_js_1 = require("./facebook-platform-integration.service.js");
|
|
20
|
+
const google_platform_integration_service_js_1 = require("./google-platform-integration.service.js");
|
|
21
|
+
const merchant_strategy_service_js_1 = require("./merchant-strategy.service.js");
|
|
22
|
+
const platform_integration_service_js_1 = require("./platform-integration.service.js");
|
|
23
|
+
let SubscriberService = class SubscriberService {
|
|
24
|
+
constructor(options, eventBus, integrationService, googleService, facebookService, strategy, productService) {
|
|
25
|
+
this.options = options;
|
|
26
|
+
this.eventBus = eventBus;
|
|
27
|
+
this.integrationService = integrationService;
|
|
28
|
+
this.googleService = googleService;
|
|
29
|
+
this.facebookService = facebookService;
|
|
30
|
+
this.strategy = strategy;
|
|
31
|
+
this.productService = productService;
|
|
32
|
+
}
|
|
33
|
+
async onApplicationBootstrap() {
|
|
34
|
+
this.eventBus.ofType(core_1.ProductEvent).subscribe(async (props) => {
|
|
35
|
+
await this.event(props);
|
|
36
|
+
});
|
|
37
|
+
this.eventBus
|
|
38
|
+
.ofType(core_1.ProductVariantEvent)
|
|
39
|
+
.subscribe(async ({ ctx, entity, type, input }) => {
|
|
40
|
+
if (!entity || !entity.length)
|
|
41
|
+
return;
|
|
42
|
+
const product = await this.productService.findOne(ctx, entity[0].productId);
|
|
43
|
+
if (!product)
|
|
44
|
+
return;
|
|
45
|
+
await this.event({ ctx, entity: product, type, input });
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
async event({ ctx, entity, type, input, }) {
|
|
49
|
+
if (!this.isValidEventData(ctx, entity))
|
|
50
|
+
return;
|
|
51
|
+
const { googleAutoUpdate, facebookAutoUpdate } = await this.integrationService.getPlatformAutoUpdateSettings(ctx);
|
|
52
|
+
if (!googleAutoUpdate && !facebookAutoUpdate)
|
|
53
|
+
return;
|
|
54
|
+
const data = await this.strategy.getBaseData(ctx, entity);
|
|
55
|
+
if (!data)
|
|
56
|
+
return;
|
|
57
|
+
const operations = this.getOperationsForType(type);
|
|
58
|
+
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
|
+
]);
|
|
67
|
+
}
|
|
68
|
+
isValidEventData(ctx, entity) {
|
|
69
|
+
return Boolean(ctx && entity && entity instanceof core_1.Product);
|
|
70
|
+
}
|
|
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];
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
exports.SubscriberService = SubscriberService;
|
|
81
|
+
exports.SubscriberService = SubscriberService = __decorate([
|
|
82
|
+
(0, common_1.Injectable)(),
|
|
83
|
+
__param(0, (0, common_1.Inject)(constants_js_1.MERCHANT_PLUGIN_OPTIONS)),
|
|
84
|
+
__metadata("design:paramtypes", [Object, core_1.EventBus,
|
|
85
|
+
platform_integration_service_js_1.PlatformIntegrationService,
|
|
86
|
+
google_platform_integration_service_js_1.GooglePlatformIntegrationService,
|
|
87
|
+
facebook_platform_integration_service_js_1.FacebookPlatformIntegrationService,
|
|
88
|
+
merchant_strategy_service_js_1.MerchantStrategyService,
|
|
89
|
+
core_1.ProductService])
|
|
90
|
+
], SubscriberService);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Product, RequestContext } from "@deenruv/core";
|
|
2
|
+
import { BaseData, BaseProductData, MerchantExportStrategy } from "../types.js";
|
|
3
|
+
export declare class DefaultMerchantExportStrategy implements MerchantExportStrategy<BaseProductData<BaseData>> {
|
|
4
|
+
prepareFacebookProductPayload(ctx: RequestContext, product: BaseProductData<BaseData>): Promise<undefined>;
|
|
5
|
+
prepareGoogleProductPayload(ctx: RequestContext, product: BaseProductData<BaseData>): Promise<undefined>;
|
|
6
|
+
getBaseData(ctx: RequestContext, product: Product): Promise<{
|
|
7
|
+
communicateID: string;
|
|
8
|
+
variantID: import("@deenruv/core").ID;
|
|
9
|
+
customFields: import("@deenruv/core").CustomProductFields;
|
|
10
|
+
variants: undefined;
|
|
11
|
+
title: import("@deenruv/core").LocaleString;
|
|
12
|
+
description: import("@deenruv/core").LocaleString;
|
|
13
|
+
slug: import("@deenruv/core").LocaleString;
|
|
14
|
+
asset: string;
|
|
15
|
+
price: number;
|
|
16
|
+
currencyCode: import("@deenruv/core").CurrencyCode;
|
|
17
|
+
availability: string;
|
|
18
|
+
colors: never[];
|
|
19
|
+
mpn: string;
|
|
20
|
+
}[]>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DefaultMerchantExportStrategy = void 0;
|
|
4
|
+
class DefaultMerchantExportStrategy {
|
|
5
|
+
async prepareFacebookProductPayload(ctx, product) {
|
|
6
|
+
return undefined;
|
|
7
|
+
}
|
|
8
|
+
async prepareGoogleProductPayload(ctx, product) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
async getBaseData(ctx, product) {
|
|
12
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
13
|
+
return [
|
|
14
|
+
{
|
|
15
|
+
communicateID: product.id,
|
|
16
|
+
variantID: (_b = (_a = product.variants) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.id,
|
|
17
|
+
customFields: product.customFields,
|
|
18
|
+
variants: undefined,
|
|
19
|
+
title: product.name,
|
|
20
|
+
description: product.description,
|
|
21
|
+
slug: product.slug,
|
|
22
|
+
asset: (_d = (_c = product.featuredAsset) === null || _c === void 0 ? void 0 : _c.preview) !== null && _d !== void 0 ? _d : (_g = (_f = (_e = product.assets) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.asset) === null || _g === void 0 ? void 0 : _g.preview,
|
|
23
|
+
price: (_j = (_h = product.variants) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.priceWithTax,
|
|
24
|
+
currencyCode: (_l = (_k = product.variants) === null || _k === void 0 ? void 0 : _k[0]) === null || _l === void 0 ? void 0 : _l.currencyCode,
|
|
25
|
+
availability: ((_p = (_o = (_m = product.variants) === null || _m === void 0 ? void 0 : _m[0]) === null || _o === void 0 ? void 0 : _o.stockLevels) === null || _p === void 0 ? void 0 : _p[0].stockOnHand) > 0
|
|
26
|
+
? "in stock"
|
|
27
|
+
: "out of stock",
|
|
28
|
+
colors: [],
|
|
29
|
+
mpn: (_r = (_q = product.variants) === null || _q === void 0 ? void 0 : _q[0]) === null || _r === void 0 ? void 0 : _r.sku,
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.DefaultMerchantExportStrategy = DefaultMerchantExportStrategy;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { InjectableStrategy, Product, RequestContext } from "@deenruv/core";
|
|
2
|
+
import { content_v2_1 } from "googleapis";
|
|
3
|
+
export type BaseData = {
|
|
4
|
+
communicateID: string;
|
|
5
|
+
variantID: string | number;
|
|
6
|
+
};
|
|
7
|
+
export type BaseProductData<T extends BaseData> = Array<T>;
|
|
8
|
+
export type MerchantPluginOptions = {
|
|
9
|
+
strategy?: MerchantExportStrategy<BaseProductData<any>>;
|
|
10
|
+
};
|
|
11
|
+
export type GoogleProduct = Omit<content_v2_1.Schema$Product, "brand"> & BaseData;
|
|
12
|
+
export type FacebookProduct = Record<string, unknown> & {
|
|
13
|
+
communicateID: string;
|
|
14
|
+
variantID: string | number;
|
|
15
|
+
};
|
|
16
|
+
export interface MerchantExportStrategy<T extends BaseProductData<any>> extends InjectableStrategy {
|
|
17
|
+
getBaseData: (ctx: RequestContext, product: Product) => Promise<T | undefined>;
|
|
18
|
+
prepareGoogleProductPayload: (ctx: RequestContext, data: T) => Promise<Array<GoogleProduct> | undefined>;
|
|
19
|
+
prepareFacebookProductPayload: (ctx: RequestContext, data: T) => Promise<Array<FacebookProduct> | undefined>;
|
|
20
|
+
}
|
|
21
|
+
declare module "@deenruv/core" {
|
|
22
|
+
interface CustomProductFields {
|
|
23
|
+
seoTitle: string | null;
|
|
24
|
+
seoDescription: string | null;
|
|
25
|
+
}
|
|
26
|
+
interface CustomProductVariantFields {
|
|
27
|
+
communicateID: string | null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { $, Selector } from "../zeus/index";
|
|
2
|
+
import { typedGql } from "../zeus/typedDocumentNode";
|
|
3
|
+
import { scalars } from "./scalars";
|
|
4
|
+
|
|
5
|
+
const mutation = typedGql("mutation", { scalars });
|
|
6
|
+
|
|
7
|
+
export const merchantPlatformSettingsSelector = Selector(
|
|
8
|
+
"MerchantPlatformSettingsEntity",
|
|
9
|
+
)({
|
|
10
|
+
platform: true,
|
|
11
|
+
entries: { key: true, value: true },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const saveMerchantPlatformSettings = mutation({
|
|
15
|
+
saveMerchantPlatformSettings: [
|
|
16
|
+
{ input: $("input", "SaveMerchantPlatformSettingInput!") },
|
|
17
|
+
merchantPlatformSettingsSelector,
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const removeOrphanItems = mutation({
|
|
22
|
+
removeOrphanItems: [{ platform: $("platform", "String!") }, true],
|
|
23
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { typedGql } from "../zeus/typedDocumentNode";
|
|
2
|
+
import { scalars } from "./scalars";
|
|
3
|
+
import { merchantPlatformSettingsSelector } from "./mutations";
|
|
4
|
+
import { $ } from "../zeus/index";
|
|
5
|
+
|
|
6
|
+
const query = typedGql("query", { scalars });
|
|
7
|
+
export const getMerchantPlatformSettings = query({
|
|
8
|
+
getMerchantPlatformSettings: [
|
|
9
|
+
{ platform: $("platform", "String!") },
|
|
10
|
+
merchantPlatformSettingsSelector,
|
|
11
|
+
],
|
|
12
|
+
});
|
|
13
|
+
export const getMerchantPlatformInfo = query({
|
|
14
|
+
getMerchantPlatformInfo: [
|
|
15
|
+
{ platform: $("platform", "String!") },
|
|
16
|
+
{ isValidConnection: true, productsCount: true },
|
|
17
|
+
],
|
|
18
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { ZeusScalars } from "../zeus/index";
|
|
2
|
+
|
|
3
|
+
export const scalars = ZeusScalars({
|
|
4
|
+
DateTime: {
|
|
5
|
+
decode: (e: unknown) => new Date(e as string).toISOString(),
|
|
6
|
+
encode: (e: unknown) => (e as Date).toISOString(),
|
|
7
|
+
},
|
|
8
|
+
JSON: {
|
|
9
|
+
decode: (e: unknown) => JSON.parse(e as string),
|
|
10
|
+
encode: (e: unknown) => JSON.stringify(e),
|
|
11
|
+
},
|
|
12
|
+
});
|