@deenruv/merchant-plugin 1.0.15 → 1.0.17-dev.14

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.
@@ -35,6 +35,7 @@ const core_1 = require("@deenruv/core");
35
35
  const common_1 = require("@nestjs/common");
36
36
  const platform_integration_settings_entity_js_1 = require("../entities/platform-integration-settings.entity.js");
37
37
  const facebook_platform_integration_service_js_1 = require("./facebook-platform-integration.service.js");
38
+ const google_merchant_api_js_1 = require("./google-merchant-api.js");
38
39
  const google_platform_integration_service_js_1 = require("./google-platform-integration.service.js");
39
40
  const merchant_strategy_service_js_1 = require("./merchant-strategy.service.js");
40
41
  const BATCH_SIZE = 200;
@@ -127,6 +128,9 @@ let PlatformIntegrationService = class PlatformIntegrationService {
127
128
  else {
128
129
  this.log("Error sending products to google");
129
130
  googleResponse = false;
131
+ throw response.error instanceof Error
132
+ ? response.error
133
+ : new Error("Google product sync failed");
130
134
  }
131
135
  }
132
136
  if (platform === "facebook") {
@@ -141,6 +145,7 @@ let PlatformIntegrationService = class PlatformIntegrationService {
141
145
  else {
142
146
  this.log("Error sending products to facebook");
143
147
  facebookResponse = false;
148
+ throw new Error(response.message || "Facebook product sync failed");
144
149
  }
145
150
  }
146
151
  return { status: "SUCCESS", facebookResponse, googleResponse };
@@ -166,33 +171,40 @@ let PlatformIntegrationService = class PlatformIntegrationService {
166
171
  const service = map[platform];
167
172
  if (!service)
168
173
  throw new Error("Unknown platform");
169
- const results = await service.getAllProducts(ctx);
174
+ const remoteProducts = await service.getAllProducts(ctx);
170
175
  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");
176
+ const { totalItems } = await this.productService.findAll(ctx, {
177
+ take: 1,
178
+ skip: 0,
179
+ });
180
+ const workers = Math.ceil(totalItems / WORKER_THRESHOLD);
181
+ for (let worker = 0; worker < workers; worker++) {
182
+ try {
183
+ for (var _d = true, _e = (e_2 = void 0, __asyncValues(this.fetchProducts({ ctx, worker }, (progress) => {
184
+ if (job.state === admin_types_1.JobState.CANCELLED) {
185
+ throw new Error("Job was cancelled");
186
+ }
187
+ else
188
+ job.setProgress(progress);
189
+ }))), _f; _f = await _e.next(), _a = _f.done, !_a; _d = true) {
190
+ _c = _f.value;
191
+ _d = false;
192
+ const product = _c;
193
+ products.push(...product);
175
194
  }
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
195
  }
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);
196
+ catch (e_2_1) { e_2 = { error: e_2_1 }; }
197
+ finally {
198
+ try {
199
+ if (!_d && !_a && (_b = _e.return)) await _b.call(_e);
200
+ }
201
+ finally { if (e_2) throw e_2.error; }
189
202
  }
190
- finally { if (e_2) throw e_2.error; }
191
203
  }
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);
204
+ const orphanProducts = (0, google_merchant_api_js_1.selectRemoteOrphanProducts)(remoteProducts, products);
205
+ if (orphanProducts.length > 0) {
206
+ this.log(`Found ${orphanProducts.length} orphan items for platform ${platform}`);
207
+ await service.removeOrphanItems(ctx, orphanProducts);
196
208
  }
197
209
  else {
198
210
  this.log(`No orphan items found for platform ${platform}`);
@@ -1,22 +1,31 @@
1
1
  import { InjectableStrategy, Product, RequestContext } from "@deenruv/core";
2
- import { content_v2_1 } from "googleapis";
2
+ import type { protos } from "@google-shopping/products";
3
3
  export type BaseData = {
4
4
  communicateID: string;
5
5
  variantID: string | number;
6
6
  };
7
7
  export type BaseProductData<T extends BaseData> = Array<T>;
8
8
  export type MerchantPluginOptions = {
9
- strategy?: MerchantExportStrategy<BaseProductData<any>>;
9
+ strategy?: MerchantExportStrategy<BaseProductData<BaseData>>;
10
+ };
11
+ export type GoogleProductInput = protos.google.shopping.merchant.products.v1.IProductInput;
12
+ export type GoogleProcessedProduct = protos.google.shopping.merchant.products.v1.IProduct;
13
+ export type GoogleProduct = BaseData & {
14
+ productAttributes: NonNullable<GoogleProductInput["productAttributes"]>;
15
+ customAttributes?: GoogleProductInput["customAttributes"];
16
+ versionNumber?: GoogleProductInput["versionNumber"];
17
+ };
18
+ export type RemoteProduct = Pick<BaseData, "communicateID"> & {
19
+ name?: string;
10
20
  };
11
- export type GoogleProduct = Omit<content_v2_1.Schema$Product, "brand"> & BaseData;
12
21
  export type FacebookProduct = Record<string, unknown> & {
13
22
  communicateID: string;
14
23
  variantID: string | number;
15
24
  };
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>;
25
+ export interface MerchantExportStrategy<T extends BaseProductData<BaseData>> extends InjectableStrategy {
26
+ getBaseData(ctx: RequestContext, product: Product): Promise<T | undefined>;
27
+ prepareGoogleProductPayload(ctx: RequestContext, data: T): Promise<Array<GoogleProduct> | undefined>;
28
+ prepareFacebookProductPayload(ctx: RequestContext, data: T): Promise<Array<FacebookProduct> | undefined>;
20
29
  }
21
30
  declare module "@deenruv/core" {
22
31
  interface CustomProductFields {
@@ -54,6 +54,7 @@ export const GooglePage = () => {
54
54
  const [settingsForm, setSettingsForm] = useState({
55
55
  brand: "",
56
56
  merchantId: "",
57
+ dataSource: "",
57
58
  credentials: "",
58
59
  autoUpdate: true,
59
60
  firstSync: true,
@@ -75,6 +76,9 @@ export const GooglePage = () => {
75
76
  if (key === "merchantId") {
76
77
  acc.merchantId = value;
77
78
  }
79
+ if (key === "dataSource") {
80
+ acc.dataSource = value;
81
+ }
78
82
  if (key === "credentials") {
79
83
  acc.credentials = value;
80
84
  }
@@ -89,6 +93,7 @@ export const GooglePage = () => {
89
93
  {} as {
90
94
  brand: string;
91
95
  merchantId: string;
96
+ dataSource: string;
92
97
  credentials: string;
93
98
  autoUpdate: boolean;
94
99
  firstSync: boolean;
@@ -183,6 +188,7 @@ export const GooglePage = () => {
183
188
  <label>Merchant ID</label>
184
189
  <input
185
190
  className="w-full"
191
+ required
186
192
  value={settingsForm.merchantId}
187
193
  onChange={(e) =>
188
194
  setSettingsForm({
@@ -193,6 +199,21 @@ export const GooglePage = () => {
193
199
  />
194
200
  </div>
195
201
  </div>
202
+ <div className="w-full flex flex-col gap-2">
203
+ <label>Data source</label>
204
+ <input
205
+ className="w-full"
206
+ placeholder="accounts/{merchantId}/dataSources/{id}"
207
+ required
208
+ value={settingsForm.dataSource}
209
+ onChange={(e) =>
210
+ setSettingsForm({
211
+ ...settingsForm,
212
+ dataSource: e.target.value,
213
+ })
214
+ }
215
+ />
216
+ </div>
196
217
  <div className="flex flex-col gap-4">
197
218
  <div className="flex flex-col">
198
219
  <label>Google Account Credentials</label>
@@ -1 +1,2 @@
1
- export declare const FacebookPage: () => import("react/jsx-runtime").JSX.Element;
1
+ import React from "react";
2
+ export declare const FacebookPage: () => React.JSX.Element;
@@ -1 +1,2 @@
1
- export declare const GooglePage: () => import("react/jsx-runtime").JSX.Element;
1
+ import React from "react";
2
+ export declare const GooglePage: () => React.JSX.Element;
@@ -41,6 +41,7 @@ export const GooglePage = () => {
41
41
  const [settingsForm, setSettingsForm] = useState({
42
42
  brand: "",
43
43
  merchantId: "",
44
+ dataSource: "",
44
45
  credentials: "",
45
46
  autoUpdate: true,
46
47
  firstSync: true,
@@ -59,6 +60,9 @@ export const GooglePage = () => {
59
60
  if (key === "merchantId") {
60
61
  acc.merchantId = value;
61
62
  }
63
+ if (key === "dataSource") {
64
+ acc.dataSource = value;
65
+ }
62
66
  if (key === "credentials") {
63
67
  acc.credentials = value;
64
68
  }
@@ -128,10 +132,13 @@ export const GooglePage = () => {
128
132
  alignItems: "center",
129
133
  width: "42px",
130
134
  height: "42px",
131
- }, className: "spinner" }) })), _jsxs("form", { className: "flex flex-col gap-4", onSubmit: onSubmit, children: [_jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Brand" }), _jsx(Input, { className: "w-full", value: settingsForm.brand, onChange: (e) => setSettingsForm({ ...settingsForm, brand: e.target.value }) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Merchant ID" }), _jsx(Input, { className: "w-full", value: settingsForm.merchantId, onChange: (e) => setSettingsForm({
135
+ }, className: "spinner" }) })), _jsxs("form", { className: "flex flex-col gap-4", onSubmit: onSubmit, children: [_jsxs("div", { className: "flex justify-between gap-4", children: [_jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Brand" }), _jsx(Input, { className: "w-full", value: settingsForm.brand, onChange: (e) => setSettingsForm({ ...settingsForm, brand: e.target.value }) })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Merchant ID" }), _jsx(Input, { className: "w-full", required: true, value: settingsForm.merchantId, onChange: (e) => setSettingsForm({
132
136
  ...settingsForm,
133
137
  merchantId: e.target.value,
134
- }) })] })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col", children: [_jsx(Label, { children: "Google Account Credentials" }), _jsx(Input, { style: {
138
+ }) })] })] }), _jsxs("div", { className: "w-full flex flex-col gap-2", children: [_jsx(Label, { children: "Data source" }), _jsx(Input, { className: "w-full", placeholder: "accounts/{merchantId}/dataSources/{id}", required: true, value: settingsForm.dataSource, onChange: (e) => setSettingsForm({
139
+ ...settingsForm,
140
+ dataSource: e.target.value,
141
+ }) })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col", children: [_jsx(Label, { children: "Google Account Credentials" }), _jsx(Input, { style: {
135
142
  border: "none",
136
143
  backgroundColor: "transparent",
137
144
  background: "none",
@@ -143,7 +150,7 @@ export const GooglePage = () => {
143
150
  firstSync: typeof checked === "boolean" ? checked : false,
144
151
  }) })] }), _jsx(Button, { children: "Save" })] }) })] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("span", { children: "Connection status" }), serviceInfo.connectionStatus ? _jsx("div", { children: "\uD83D\uDC9A" }) : _jsx("div", { children: "\uD83D\uDC94" })] }), serviceInfo.connectionStatus ? (_jsx("div", { className: "mt-8", children: _jsx(Button, { onClick: async () => {
145
152
  try {
146
- await removeOldItems({ platform: "facebook" });
153
+ await removeOldItems({ platform: "google" });
147
154
  toast.success("Old items removed successfully");
148
155
  refetch();
149
156
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deenruv/merchant-plugin",
3
- "version": "1.0.15",
3
+ "version": "1.0.17-dev.14",
4
4
  "license": "MIT",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -16,27 +16,26 @@
16
16
  "./plugin-ui": "./dist/plugin-ui/index.js"
17
17
  },
18
18
  "dependencies": {
19
+ "@google-shopping/products": "0.9.0",
19
20
  "@nestjs/common": "~10.3.10",
20
21
  "@nestjs/graphql": "~12.2.0",
21
22
  "date-fns": "^4.1.0",
22
23
  "facebook-nodejs-business-sdk": "^23.0.1",
23
- "google-auth-library": "^9.15.0",
24
- "googleapis": "^144.0.0",
25
24
  "graphql": "~16.9.0",
26
25
  "graphql-tag": "^2.12.6",
27
26
  "graphql-zeus": "^5.4.2",
28
27
  "html-to-text": "^9.0.5",
29
- "lucide-react": "^0.363.0",
28
+ "lucide-react": "^1.21.0",
30
29
  "react": "^19.0.0",
31
30
  "react-dom": "^19.0.0",
32
31
  "react-i18next": "^14.0.5",
33
- "sonner": "^1.4.41",
34
32
  "recharts": "^2.12.7",
35
- "@deenruv/admin-types": "^1.0.15",
36
- "@deenruv/common": "^1.0.15",
37
- "@deenruv/ui-devkit": "^1.0.15",
38
- "@deenruv/react-ui-devkit": "^1.0.15",
39
- "@deenruv/admin-ui": "^1.0.15"
33
+ "sonner": "^1.4.41",
34
+ "@deenruv/admin-types": "^1.0.17-dev.14",
35
+ "@deenruv/common": "^1.0.17-dev.14",
36
+ "@deenruv/admin-ui": "^1.0.17-dev.14",
37
+ "@deenruv/react-ui-devkit": "^1.0.17-dev.14",
38
+ "@deenruv/ui-devkit": "^1.0.17-dev.14"
40
39
  },
41
40
  "devDependencies": {
42
41
  "@graphql-typed-document-node/core": "3.2.0",
@@ -48,16 +47,17 @@
48
47
  "rimraf": "^5.0.10",
49
48
  "tslib": "^2.6.2",
50
49
  "typescript": "5.3.3",
51
- "@deenruv/core": "^1.0.15"
50
+ "@deenruv/core": "^1.0.17-dev.14"
52
51
  },
53
52
  "peerDependencies": {
54
- "@deenruv/core": "^1.0.0"
53
+ "@deenruv/core": "^1.0.0 || ^1.0.17-dev.0"
55
54
  },
56
55
  "scripts": {
57
56
  "build": "rimraf dist && tsc --build && cp -r ./src/plugin-server/ui ./dist/plugin-server",
58
57
  "watch": "concurrently -k -p \"[{name}]\" -n \"SERVER,UI\" -c \"yellow.bold,cyan.bold\" \"tsc --watch --project src/plugin-server/tsconfig.json\" \"tsc --watch --project src/plugin-ui/tsconfig.json\"",
59
58
  "lint": "eslint ./src/**/*.ts",
60
59
  "lint:fix": "eslint --fix ./src/**/*.ts",
60
+ "test": "vitest --run --exclude='src/plugin-server/e2e/**'",
61
61
  "zeus": "zeus http://localhost:6100/admin-api ./src/plugin-server --es && zeus http://localhost:6100/admin-api ./src/plugin-server/ui --td && zeus http://localhost:6100/admin-api ./src/plugin-ui --td --es"
62
62
  }
63
63
  }