@reactionary/magento 0.0.1 → 0.10.19
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 +21 -0
- package/capabilities/cart.capability.js +22 -22
- package/capabilities/category.capability.js +10 -9
- package/capabilities/checkout.capability.js +308 -0
- package/capabilities/identity.capability.js +31 -16
- package/capabilities/inventory.capability.js +2 -2
- package/capabilities/order-search.capability.js +150 -0
- package/capabilities/order.capability.js +51 -0
- package/capabilities/price.capability.js +2 -2
- package/capabilities/product-associations.capability.js +118 -0
- package/capabilities/product-recommendations.capability.js +148 -0
- package/capabilities/product-reviews.capability.js +182 -0
- package/capabilities/product-search.capability.js +2 -2
- package/capabilities/product.capability.js +6 -5
- package/capabilities/profile.capability.js +283 -0
- package/core/client.js +324 -24
- package/core/initialize.js +150 -2
- package/core/product-reviews.graphql.js +72 -0
- package/factories/category/category.factory.js +1 -1
- package/factories/checkout/checkout.factory.js +203 -0
- package/factories/index.js +6 -0
- package/factories/order/order.factory.js +115 -0
- package/factories/order-search/order-search.factory.js +79 -0
- package/factories/product/product.factory.js +23 -18
- package/factories/product-associations/product-associations.factory.js +25 -0
- package/factories/product-reviews/product-reviews.factory.js +65 -0
- package/factories/product-search/product-search.factory.js +3 -4
- package/factories/profile/profile.factory.js +61 -0
- package/index.js +9 -0
- package/package.json +11 -6
- package/schema/capabilities.schema.js +16 -2
- package/schema/configuration.schema.js +5 -1
- package/src/capabilities/cart.capability.d.ts +5 -0
- package/src/capabilities/checkout.capability.d.ts +30 -0
- package/src/capabilities/order-search.capability.d.ts +16 -0
- package/src/capabilities/order.capability.d.ts +12 -0
- package/src/capabilities/product-associations.capability.d.ts +23 -0
- package/src/capabilities/product-recommendations.capability.d.ts +24 -0
- package/src/capabilities/product-reviews.capability.d.ts +35 -0
- package/src/capabilities/profile.capability.d.ts +24 -0
- package/src/core/client.d.ts +77 -2
- package/src/core/initialize.types.d.ts +40 -7
- package/src/core/product-reviews.graphql.d.ts +10 -0
- package/src/factories/category/category.factory.d.ts +2 -2
- package/src/factories/checkout/checkout.factory.d.ts +32 -0
- package/src/factories/index.d.ts +6 -0
- package/src/factories/order/order.factory.d.ts +77 -0
- package/src/factories/order-search/order-search.factory.d.ts +43 -0
- package/src/factories/product/product.factory.d.ts +2 -1
- package/src/factories/product-associations/product-associations.factory.d.ts +15 -0
- package/src/factories/product-reviews/product-reviews.factory.d.ts +40 -0
- package/src/factories/profile/profile.factory.d.ts +43 -0
- package/src/index.d.ts +9 -0
- package/src/schema/capabilities.schema.d.ts +50 -2
- package/src/schema/configuration.schema.d.ts +7 -0
- package/src/schema/magento.types.d.ts +219 -0
- package/src/utils/magento-product-lookup.d.ts +15 -0
- package/src/utils/magento-product.d.ts +36 -0
- package/src/utils/magento-review.d.ts +20 -0
- package/utils/magento-product-lookup.js +76 -0
- package/utils/magento-product.js +110 -0
- package/utils/magento-review.js +34 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
import {
|
|
12
|
+
OrderSearchCapability,
|
|
13
|
+
OrderSearchQueryByTermSchema,
|
|
14
|
+
OrderSearchResultSchema,
|
|
15
|
+
Reactionary,
|
|
16
|
+
success
|
|
17
|
+
} from "@reactionary/core";
|
|
18
|
+
import createDebug from "debug";
|
|
19
|
+
const debug = createDebug("reactionary:magento:order-search");
|
|
20
|
+
class MagentoOrderSearchCapability extends OrderSearchCapability {
|
|
21
|
+
constructor(config, cache, context, magentoApi, factory) {
|
|
22
|
+
super(cache, context);
|
|
23
|
+
this.magentoApi = magentoApi;
|
|
24
|
+
this.config = config;
|
|
25
|
+
this.factory = factory;
|
|
26
|
+
}
|
|
27
|
+
magentoApi;
|
|
28
|
+
config;
|
|
29
|
+
factory;
|
|
30
|
+
emptyResult(payload) {
|
|
31
|
+
return this.factory.parseOrderSearchResult(
|
|
32
|
+
this.context,
|
|
33
|
+
{
|
|
34
|
+
items: [],
|
|
35
|
+
total_count: 0,
|
|
36
|
+
search_criteria: {
|
|
37
|
+
page_size: payload.search.paginationOptions.pageSize,
|
|
38
|
+
current_page: payload.search.paginationOptions.pageNumber
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
payload
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
async resolveCustomerId() {
|
|
45
|
+
try {
|
|
46
|
+
const me = await this.magentoApi.getMe();
|
|
47
|
+
return me?.id !== void 0 ? String(me.id) : null;
|
|
48
|
+
} catch (err) {
|
|
49
|
+
debug("resolveCustomerId: not authenticated: %O", err);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
buildSearchParams(payload, customerId) {
|
|
54
|
+
const params = new URLSearchParams();
|
|
55
|
+
let group = 0;
|
|
56
|
+
const addFilter = (field, value, condition = "eq") => {
|
|
57
|
+
params.set(
|
|
58
|
+
`searchCriteria[filterGroups][${group}][filters][0][field]`,
|
|
59
|
+
field
|
|
60
|
+
);
|
|
61
|
+
params.set(
|
|
62
|
+
`searchCriteria[filterGroups][${group}][filters][0][value]`,
|
|
63
|
+
value
|
|
64
|
+
);
|
|
65
|
+
params.set(
|
|
66
|
+
`searchCriteria[filterGroups][${group}][filters][0][condition_type]`,
|
|
67
|
+
condition
|
|
68
|
+
);
|
|
69
|
+
group += 1;
|
|
70
|
+
};
|
|
71
|
+
addFilter("customer_id", customerId);
|
|
72
|
+
if (payload.search.term) {
|
|
73
|
+
addFilter("increment_id", `%${payload.search.term}%`, "like");
|
|
74
|
+
}
|
|
75
|
+
if (payload.search.orderStatus && payload.search.orderStatus.length > 0) {
|
|
76
|
+
const magentoStatuses = Array.from(
|
|
77
|
+
new Set(
|
|
78
|
+
payload.search.orderStatus.flatMap((status) => {
|
|
79
|
+
switch (status) {
|
|
80
|
+
case "ReleasedToFulfillment":
|
|
81
|
+
return ["processing"];
|
|
82
|
+
case "Shipped":
|
|
83
|
+
return ["complete", "closed"];
|
|
84
|
+
case "Cancelled":
|
|
85
|
+
return ["canceled"];
|
|
86
|
+
default:
|
|
87
|
+
return ["pending"];
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
)
|
|
91
|
+
);
|
|
92
|
+
addFilter("status", magentoStatuses.join(","), "in");
|
|
93
|
+
}
|
|
94
|
+
if (payload.search.startDate) {
|
|
95
|
+
addFilter("created_at", payload.search.startDate, "gteq");
|
|
96
|
+
}
|
|
97
|
+
if (payload.search.endDate) {
|
|
98
|
+
addFilter("created_at", payload.search.endDate, "lteq");
|
|
99
|
+
}
|
|
100
|
+
params.set(
|
|
101
|
+
"searchCriteria[pageSize]",
|
|
102
|
+
String(payload.search.paginationOptions.pageSize)
|
|
103
|
+
);
|
|
104
|
+
params.set(
|
|
105
|
+
"searchCriteria[currentPage]",
|
|
106
|
+
String(payload.search.paginationOptions.pageNumber)
|
|
107
|
+
);
|
|
108
|
+
params.set("searchCriteria[sortOrders][0][field]", "created_at");
|
|
109
|
+
params.set("searchCriteria[sortOrders][0][direction]", "DESC");
|
|
110
|
+
return params;
|
|
111
|
+
}
|
|
112
|
+
async queryByTerm(payload) {
|
|
113
|
+
const customerId = await this.resolveCustomerId();
|
|
114
|
+
if (!customerId) {
|
|
115
|
+
return success(this.emptyResult(payload));
|
|
116
|
+
}
|
|
117
|
+
let response = await this.magentoApi.searchOrders(
|
|
118
|
+
this.buildSearchParams(payload, customerId)
|
|
119
|
+
);
|
|
120
|
+
if (payload.search.partNumber && payload.search.partNumber.length > 0) {
|
|
121
|
+
response = this.filterByPartNumber(response, payload.search.partNumber);
|
|
122
|
+
}
|
|
123
|
+
const result = this.factory.parseOrderSearchResult(
|
|
124
|
+
this.context,
|
|
125
|
+
response,
|
|
126
|
+
payload
|
|
127
|
+
);
|
|
128
|
+
return success(result);
|
|
129
|
+
}
|
|
130
|
+
filterByPartNumber(response, partNumbers) {
|
|
131
|
+
const items = (response.items ?? []).filter((order) => {
|
|
132
|
+
const skus = (order.items ?? []).map((i) => i.sku);
|
|
133
|
+
return partNumbers.every((pn) => skus.includes(pn));
|
|
134
|
+
});
|
|
135
|
+
return {
|
|
136
|
+
...response,
|
|
137
|
+
items,
|
|
138
|
+
total_count: items.length
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
__decorateClass([
|
|
143
|
+
Reactionary({
|
|
144
|
+
inputSchema: OrderSearchQueryByTermSchema,
|
|
145
|
+
outputSchema: OrderSearchResultSchema
|
|
146
|
+
})
|
|
147
|
+
], MagentoOrderSearchCapability.prototype, "queryByTerm", 1);
|
|
148
|
+
export {
|
|
149
|
+
MagentoOrderSearchCapability
|
|
150
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
import {
|
|
12
|
+
OrderCapability,
|
|
13
|
+
OrderQueryByIdSchema,
|
|
14
|
+
OrderSchema,
|
|
15
|
+
Reactionary,
|
|
16
|
+
error,
|
|
17
|
+
success
|
|
18
|
+
} from "@reactionary/core";
|
|
19
|
+
import createDebug from "debug";
|
|
20
|
+
const debug = createDebug("reactionary:magento:order");
|
|
21
|
+
class MagentoOrderCapability extends OrderCapability {
|
|
22
|
+
constructor(config, cache, context, magentoApi, factory) {
|
|
23
|
+
super(cache, context);
|
|
24
|
+
this.magentoApi = magentoApi;
|
|
25
|
+
this.config = config;
|
|
26
|
+
this.factory = factory;
|
|
27
|
+
}
|
|
28
|
+
magentoApi;
|
|
29
|
+
config;
|
|
30
|
+
factory;
|
|
31
|
+
async getById(payload) {
|
|
32
|
+
debug("getById %s", payload.order.key);
|
|
33
|
+
const data = await this.magentoApi.getOrderById(payload.order.key);
|
|
34
|
+
if (!data) {
|
|
35
|
+
return error({
|
|
36
|
+
type: "NotFound",
|
|
37
|
+
identifier: payload.order
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return success(this.factory.parseOrder(this.context, data));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
__decorateClass([
|
|
44
|
+
Reactionary({
|
|
45
|
+
inputSchema: OrderQueryByIdSchema,
|
|
46
|
+
outputSchema: OrderSchema
|
|
47
|
+
})
|
|
48
|
+
], MagentoOrderCapability.prototype, "getById", 1);
|
|
49
|
+
export {
|
|
50
|
+
MagentoOrderCapability
|
|
51
|
+
};
|
|
@@ -5,8 +5,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
5
5
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
6
|
if (decorator = decorators[i])
|
|
7
7
|
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
-
if (kind && result)
|
|
9
|
-
__defProp(target, key, result);
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
10
9
|
return result;
|
|
11
10
|
};
|
|
12
11
|
import {
|
|
@@ -26,6 +25,7 @@ class MagentoPriceCapability extends PriceCapability {
|
|
|
26
25
|
this.config = config;
|
|
27
26
|
this.factory = factory;
|
|
28
27
|
}
|
|
28
|
+
magentoApi;
|
|
29
29
|
config;
|
|
30
30
|
factory;
|
|
31
31
|
async getListPrice(payload) {
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
import {
|
|
12
|
+
ProductAssociationsCapability,
|
|
13
|
+
Reactionary,
|
|
14
|
+
success
|
|
15
|
+
} from "@reactionary/core";
|
|
16
|
+
import createDebug from "debug";
|
|
17
|
+
import {
|
|
18
|
+
fetchProductsBySkus,
|
|
19
|
+
resolveProductSku
|
|
20
|
+
} from "../utils/magento-product-lookup.js";
|
|
21
|
+
const debug = createDebug("reactionary:magento:product-associations");
|
|
22
|
+
class MagentoProductAssociationsCapability extends ProductAssociationsCapability {
|
|
23
|
+
constructor(config, cache, context, magentoApi, factory) {
|
|
24
|
+
super(cache, context);
|
|
25
|
+
this.magentoApi = magentoApi;
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.factory = factory;
|
|
28
|
+
}
|
|
29
|
+
magentoApi;
|
|
30
|
+
config;
|
|
31
|
+
factory;
|
|
32
|
+
getAccessoriesLinkType() {
|
|
33
|
+
return "related";
|
|
34
|
+
}
|
|
35
|
+
getSparepartsLinkType() {
|
|
36
|
+
return "crosssell";
|
|
37
|
+
}
|
|
38
|
+
getReplacementsLinkType() {
|
|
39
|
+
return "upsell";
|
|
40
|
+
}
|
|
41
|
+
async fetchAssociatedProductsFor(forProduct, linkType, maxNumberOfAssociations) {
|
|
42
|
+
const sku = await resolveProductSku(this.magentoApi, forProduct.key);
|
|
43
|
+
if (!sku) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const links = await this.magentoApi.getProductLinks(sku, linkType);
|
|
48
|
+
const linkedSkus = [...links].sort((a, b) => (a.position ?? 0) - (b.position ?? 0)).map((link) => link.linked_product_sku).slice(0, maxNumberOfAssociations);
|
|
49
|
+
return await fetchProductsBySkus(this.magentoApi, linkedSkus);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
debug("Failed to load %s links for %s: %O", linkType, sku, err);
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
toAssociations(forProduct, associationType, products) {
|
|
56
|
+
return products.map(
|
|
57
|
+
(product) => this.factory.parseAssociation(this.context, {
|
|
58
|
+
product,
|
|
59
|
+
identifier: {
|
|
60
|
+
key: `${forProduct.key}-${associationType}-${product.sku}`
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
async getAccessories(query) {
|
|
66
|
+
const products = await this.fetchAssociatedProductsFor(
|
|
67
|
+
query.forProduct,
|
|
68
|
+
this.getAccessoriesLinkType(),
|
|
69
|
+
query.numberOfAccessories
|
|
70
|
+
);
|
|
71
|
+
return success(this.toAssociations(query.forProduct, "accessory", products));
|
|
72
|
+
}
|
|
73
|
+
async getSpareparts(query) {
|
|
74
|
+
const products = await this.fetchAssociatedProductsFor(
|
|
75
|
+
query.forProduct,
|
|
76
|
+
this.getSparepartsLinkType(),
|
|
77
|
+
query.numberOfSpareparts
|
|
78
|
+
);
|
|
79
|
+
return success(this.toAssociations(query.forProduct, "sparepart", products));
|
|
80
|
+
}
|
|
81
|
+
async getReplacements(query) {
|
|
82
|
+
const products = await this.fetchAssociatedProductsFor(
|
|
83
|
+
query.forProduct,
|
|
84
|
+
this.getReplacementsLinkType(),
|
|
85
|
+
query.numberOfReplacements
|
|
86
|
+
);
|
|
87
|
+
return success(
|
|
88
|
+
this.toAssociations(query.forProduct, "replacement", products)
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
__decorateClass([
|
|
93
|
+
Reactionary({
|
|
94
|
+
cache: true,
|
|
95
|
+
cacheTimeToLiveInSeconds: 300,
|
|
96
|
+
currencyDependentCaching: false,
|
|
97
|
+
localeDependentCaching: false
|
|
98
|
+
})
|
|
99
|
+
], MagentoProductAssociationsCapability.prototype, "getAccessories", 1);
|
|
100
|
+
__decorateClass([
|
|
101
|
+
Reactionary({
|
|
102
|
+
cache: true,
|
|
103
|
+
cacheTimeToLiveInSeconds: 300,
|
|
104
|
+
currencyDependentCaching: false,
|
|
105
|
+
localeDependentCaching: false
|
|
106
|
+
})
|
|
107
|
+
], MagentoProductAssociationsCapability.prototype, "getSpareparts", 1);
|
|
108
|
+
__decorateClass([
|
|
109
|
+
Reactionary({
|
|
110
|
+
cache: true,
|
|
111
|
+
cacheTimeToLiveInSeconds: 300,
|
|
112
|
+
currencyDependentCaching: false,
|
|
113
|
+
localeDependentCaching: false
|
|
114
|
+
})
|
|
115
|
+
], MagentoProductAssociationsCapability.prototype, "getReplacements", 1);
|
|
116
|
+
export {
|
|
117
|
+
MagentoProductAssociationsCapability
|
|
118
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
import {
|
|
12
|
+
ProductRecommendationsCapability,
|
|
13
|
+
Reactionary,
|
|
14
|
+
success
|
|
15
|
+
} from "@reactionary/core";
|
|
16
|
+
import createDebug from "debug";
|
|
17
|
+
import {
|
|
18
|
+
fetchProductsBySkus,
|
|
19
|
+
fetchProductsInCategory,
|
|
20
|
+
findCategoryByName,
|
|
21
|
+
resolveProductSku
|
|
22
|
+
} from "../utils/magento-product-lookup.js";
|
|
23
|
+
import { buildProductSearchResultItem } from "../utils/magento-product.js";
|
|
24
|
+
const debug = createDebug("reactionary:magento:product-recommendations");
|
|
25
|
+
class MagentoProductRecommendationsCapability extends ProductRecommendationsCapability {
|
|
26
|
+
constructor(config, cache, context, magentoApi) {
|
|
27
|
+
super(cache, context);
|
|
28
|
+
this.magentoApi = magentoApi;
|
|
29
|
+
this.config = config;
|
|
30
|
+
}
|
|
31
|
+
magentoApi;
|
|
32
|
+
config;
|
|
33
|
+
getRelatedLinkType() {
|
|
34
|
+
return "related";
|
|
35
|
+
}
|
|
36
|
+
getSimilarLinkType() {
|
|
37
|
+
return "upsell";
|
|
38
|
+
}
|
|
39
|
+
getFrequentlyBoughtTogetherLinkType() {
|
|
40
|
+
return "crosssell";
|
|
41
|
+
}
|
|
42
|
+
toRecommendations(algorithm, keyPrefix, products) {
|
|
43
|
+
return products.map(
|
|
44
|
+
(product) => ({
|
|
45
|
+
recommendationIdentifier: {
|
|
46
|
+
key: `${keyPrefix}_${product.sku}`,
|
|
47
|
+
algorithm
|
|
48
|
+
},
|
|
49
|
+
recommendationReturnType: "productSearchResultItem",
|
|
50
|
+
product: buildProductSearchResultItem(this.config, product)
|
|
51
|
+
})
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
async fetchLinkedProducts(sourceProduct, linkType, numberOfRecommendations) {
|
|
55
|
+
const sku = await resolveProductSku(this.magentoApi, sourceProduct.key);
|
|
56
|
+
if (!sku) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const links = await this.magentoApi.getProductLinks(sku, linkType);
|
|
61
|
+
const linkedSkus = [...links].sort((a, b) => (a.position ?? 0) - (b.position ?? 0)).map((link) => link.linked_product_sku).slice(0, numberOfRecommendations);
|
|
62
|
+
return await fetchProductsBySkus(this.magentoApi, linkedSkus);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
debug("Failed to load %s links for %s: %O", linkType, sku, err);
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async getCollection(query) {
|
|
69
|
+
const category = await findCategoryByName(
|
|
70
|
+
this.magentoApi,
|
|
71
|
+
query.collectionName
|
|
72
|
+
);
|
|
73
|
+
if (!category) {
|
|
74
|
+
debug("Collection not found: %s", query.collectionName);
|
|
75
|
+
return success([]);
|
|
76
|
+
}
|
|
77
|
+
try {
|
|
78
|
+
const products = await fetchProductsInCategory(
|
|
79
|
+
this.magentoApi,
|
|
80
|
+
String(category.id),
|
|
81
|
+
query.numberOfRecommendations
|
|
82
|
+
);
|
|
83
|
+
return success(
|
|
84
|
+
this.toRecommendations("collection", String(category.id), products)
|
|
85
|
+
);
|
|
86
|
+
} catch (err) {
|
|
87
|
+
debug("Failed to load products for collection %s: %O", query.collectionName, err);
|
|
88
|
+
return success([]);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
async getRelatedProductsRecommendations(query) {
|
|
92
|
+
const products = await this.fetchLinkedProducts(
|
|
93
|
+
query.sourceProduct,
|
|
94
|
+
this.getRelatedLinkType(),
|
|
95
|
+
query.numberOfRecommendations
|
|
96
|
+
);
|
|
97
|
+
return this.toRecommendations("related", query.sourceProduct.key, products);
|
|
98
|
+
}
|
|
99
|
+
async getSimilarProductsRecommendations(query) {
|
|
100
|
+
const products = await this.fetchLinkedProducts(
|
|
101
|
+
query.sourceProduct,
|
|
102
|
+
this.getSimilarLinkType(),
|
|
103
|
+
query.numberOfRecommendations
|
|
104
|
+
);
|
|
105
|
+
return this.toRecommendations("similar", query.sourceProduct.key, products);
|
|
106
|
+
}
|
|
107
|
+
async getFrequentlyBoughtTogetherRecommendations(query) {
|
|
108
|
+
const products = await this.fetchLinkedProducts(
|
|
109
|
+
query.sourceProduct,
|
|
110
|
+
this.getFrequentlyBoughtTogetherLinkType(),
|
|
111
|
+
query.numberOfRecommendations
|
|
112
|
+
);
|
|
113
|
+
return this.toRecommendations(
|
|
114
|
+
"frequentlyBoughtTogether",
|
|
115
|
+
query.sourceProduct.key,
|
|
116
|
+
products
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
async getTrendingInCategoryRecommendations(query) {
|
|
120
|
+
const category = await findCategoryByName(
|
|
121
|
+
this.magentoApi,
|
|
122
|
+
query.sourceCategory.key
|
|
123
|
+
);
|
|
124
|
+
const categoryId = category ? String(category.id) : query.sourceCategory.key;
|
|
125
|
+
try {
|
|
126
|
+
const products = await fetchProductsInCategory(
|
|
127
|
+
this.magentoApi,
|
|
128
|
+
categoryId,
|
|
129
|
+
query.numberOfRecommendations
|
|
130
|
+
);
|
|
131
|
+
return this.toRecommendations("trendingInCategory", categoryId, products);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
debug("Failed to load trending products for category %s: %O", categoryId, err);
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
__decorateClass([
|
|
139
|
+
Reactionary({
|
|
140
|
+
cache: true,
|
|
141
|
+
cacheTimeToLiveInSeconds: 300,
|
|
142
|
+
currencyDependentCaching: false,
|
|
143
|
+
localeDependentCaching: false
|
|
144
|
+
})
|
|
145
|
+
], MagentoProductRecommendationsCapability.prototype, "getCollection", 1);
|
|
146
|
+
export {
|
|
147
|
+
MagentoProductRecommendationsCapability
|
|
148
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
import {
|
|
12
|
+
ProductRatingSummarySchema,
|
|
13
|
+
ProductReviewMutationSubmitSchema,
|
|
14
|
+
ProductReviewPaginatedResultSchema,
|
|
15
|
+
ProductReviewSchema,
|
|
16
|
+
ProductReviewsCapability,
|
|
17
|
+
ProductReviewsGetRatingSummaryQuerySchema,
|
|
18
|
+
ProductReviewsListQuerySchema,
|
|
19
|
+
Reactionary,
|
|
20
|
+
error,
|
|
21
|
+
success
|
|
22
|
+
} from "@reactionary/core";
|
|
23
|
+
import createDebug from "debug";
|
|
24
|
+
import { resolveProductSku } from "../utils/magento-product-lookup.js";
|
|
25
|
+
const debug = createDebug("reactionary:magento:product-reviews");
|
|
26
|
+
const DEFAULT_PAGE_SIZE = 20;
|
|
27
|
+
const DEFAULT_REVIEW_RATING_CODE = "Rating";
|
|
28
|
+
class MagentoProductReviewsCapability extends ProductReviewsCapability {
|
|
29
|
+
constructor(config, cache, context, magentoApi, factory) {
|
|
30
|
+
super(cache, context);
|
|
31
|
+
this.magentoApi = magentoApi;
|
|
32
|
+
this.config = config;
|
|
33
|
+
this.factory = factory;
|
|
34
|
+
}
|
|
35
|
+
magentoApi;
|
|
36
|
+
config;
|
|
37
|
+
factory;
|
|
38
|
+
/**
|
|
39
|
+
* The name of the Magento rating that carries the overall star score. Stores
|
|
40
|
+
* can define several named ratings; override to pick a different one.
|
|
41
|
+
*/
|
|
42
|
+
getReviewRatingCode() {
|
|
43
|
+
return this.config.reviewRatingCode ?? DEFAULT_REVIEW_RATING_CODE;
|
|
44
|
+
}
|
|
45
|
+
async fetchRatingSummary(sku) {
|
|
46
|
+
try {
|
|
47
|
+
return await this.magentoApi.getProductReviewSummary(sku);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
debug("Failed to load the rating summary for %s: %O", sku, err);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async fetchReviews(sku, pageSize, pageNumber) {
|
|
54
|
+
try {
|
|
55
|
+
return await this.magentoApi.getProductReviews(sku, pageSize, pageNumber);
|
|
56
|
+
} catch (err) {
|
|
57
|
+
debug("Failed to load reviews for %s: %O", sku, err);
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Magento will not accept a star count directly — the rating has to be sent
|
|
63
|
+
* as an opaque `value_id` looked up through `productReviewRatingsMetadata`.
|
|
64
|
+
*/
|
|
65
|
+
async resolveRatingInput(rating) {
|
|
66
|
+
const metadata = await this.magentoApi.getProductReviewRatingsMetadata();
|
|
67
|
+
const wantedCode = this.getReviewRatingCode().toLowerCase();
|
|
68
|
+
const entry = metadata.find((item) => item.name.toLowerCase() === wantedCode) ?? metadata[0];
|
|
69
|
+
if (!entry) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const wantedValue = String(Math.round(rating));
|
|
73
|
+
const value = entry.values.find((item) => item.value === wantedValue);
|
|
74
|
+
if (!value) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return [{ id: entry.id, value_id: value.value_id }];
|
|
78
|
+
}
|
|
79
|
+
async getRatingSummary(query) {
|
|
80
|
+
const sku = await resolveProductSku(this.magentoApi, query.product.key);
|
|
81
|
+
const product = sku ? await this.fetchRatingSummary(sku) : null;
|
|
82
|
+
return success(
|
|
83
|
+
this.factory.parseRatingSummary(this.context, {
|
|
84
|
+
product: query.product,
|
|
85
|
+
ratingSummary: product?.rating_summary,
|
|
86
|
+
reviewCount: product?.review_count
|
|
87
|
+
})
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
async findReviews(query) {
|
|
91
|
+
const pageSize = query.paginationOptions?.pageSize ?? DEFAULT_PAGE_SIZE;
|
|
92
|
+
const pageNumber = query.paginationOptions?.pageNumber ?? 1;
|
|
93
|
+
const sku = await resolveProductSku(this.magentoApi, query.product.key);
|
|
94
|
+
const product = sku ? await this.fetchReviews(sku, pageSize, pageNumber) : null;
|
|
95
|
+
const reviews = product?.reviews?.items ?? [];
|
|
96
|
+
return success(
|
|
97
|
+
this.factory.parseReviewPaginatedResult(this.context, {
|
|
98
|
+
product: query.product,
|
|
99
|
+
sku: sku ?? query.product.key,
|
|
100
|
+
reviews,
|
|
101
|
+
totalCount: product?.review_count ?? reviews.length,
|
|
102
|
+
pageSize,
|
|
103
|
+
pageNumber
|
|
104
|
+
})
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
async submitReview(mutation) {
|
|
108
|
+
if (this.context.session.identityContext.identity.type !== "Registered") {
|
|
109
|
+
return error({
|
|
110
|
+
type: "InvalidInput",
|
|
111
|
+
error: "Only registered users can submit reviews."
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const sku = await resolveProductSku(this.magentoApi, mutation.product.key);
|
|
115
|
+
if (!sku) {
|
|
116
|
+
return error({
|
|
117
|
+
type: "NotFound",
|
|
118
|
+
identifier: mutation.product
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const ratings = await this.resolveRatingInput(mutation.rating);
|
|
122
|
+
if (!ratings) {
|
|
123
|
+
return error({
|
|
124
|
+
type: "Generic",
|
|
125
|
+
message: `Magento has no review rating "${this.getReviewRatingCode()}" with a value of ${mutation.rating}. Check the store's review rating configuration.`
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
const review = await this.magentoApi.createProductReview({
|
|
129
|
+
sku,
|
|
130
|
+
nickname: mutation.authorName,
|
|
131
|
+
summary: mutation.title,
|
|
132
|
+
text: mutation.content,
|
|
133
|
+
ratings
|
|
134
|
+
});
|
|
135
|
+
if (!review) {
|
|
136
|
+
return error({
|
|
137
|
+
type: "Generic",
|
|
138
|
+
message: `Magento accepted the review for ${sku} but returned no review payload.`
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return success(
|
|
142
|
+
this.factory.parseReview(this.context, {
|
|
143
|
+
product: mutation.product,
|
|
144
|
+
sku,
|
|
145
|
+
review
|
|
146
|
+
})
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
__decorateClass([
|
|
151
|
+
Reactionary({
|
|
152
|
+
inputSchema: ProductReviewsGetRatingSummaryQuerySchema,
|
|
153
|
+
outputSchema: ProductRatingSummarySchema,
|
|
154
|
+
cache: true,
|
|
155
|
+
cacheTimeToLiveInSeconds: 300,
|
|
156
|
+
currencyDependentCaching: false,
|
|
157
|
+
localeDependentCaching: false
|
|
158
|
+
})
|
|
159
|
+
], MagentoProductReviewsCapability.prototype, "getRatingSummary", 1);
|
|
160
|
+
__decorateClass([
|
|
161
|
+
Reactionary({
|
|
162
|
+
inputSchema: ProductReviewsListQuerySchema,
|
|
163
|
+
outputSchema: ProductReviewPaginatedResultSchema,
|
|
164
|
+
cache: true,
|
|
165
|
+
cacheTimeToLiveInSeconds: 60,
|
|
166
|
+
currencyDependentCaching: false,
|
|
167
|
+
localeDependentCaching: true
|
|
168
|
+
})
|
|
169
|
+
], MagentoProductReviewsCapability.prototype, "findReviews", 1);
|
|
170
|
+
__decorateClass([
|
|
171
|
+
Reactionary({
|
|
172
|
+
inputSchema: ProductReviewMutationSubmitSchema,
|
|
173
|
+
outputSchema: ProductReviewSchema,
|
|
174
|
+
cache: false,
|
|
175
|
+
cacheTimeToLiveInSeconds: 0,
|
|
176
|
+
currencyDependentCaching: false,
|
|
177
|
+
localeDependentCaching: false
|
|
178
|
+
})
|
|
179
|
+
], MagentoProductReviewsCapability.prototype, "submitReview", 1);
|
|
180
|
+
export {
|
|
181
|
+
MagentoProductReviewsCapability
|
|
182
|
+
};
|
|
@@ -5,8 +5,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
5
5
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
6
|
if (decorator = decorators[i])
|
|
7
7
|
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
-
if (kind && result)
|
|
9
|
-
__defProp(target, key, result);
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
10
9
|
return result;
|
|
11
10
|
};
|
|
12
11
|
import {
|
|
@@ -27,6 +26,7 @@ class MagentoProductSearchCapability extends ProductSearchCapability {
|
|
|
27
26
|
this.config = config;
|
|
28
27
|
this.factory = factory;
|
|
29
28
|
}
|
|
29
|
+
magentoApi;
|
|
30
30
|
config;
|
|
31
31
|
factory;
|
|
32
32
|
async queryByTerm(payload) {
|