Skip to content

Commit fb3da4d

Browse files
committed
Merge branch 'main' into wilson-customs
2 parents 6529c50 + d3cc60b commit fb3da4d

File tree

11 files changed

+378
-52
lines changed

11 files changed

+378
-52
lines changed

CHANGELOG.md

+14
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
# [1.11.0](https://github.com/adobe-rnd/helix-commerce-api/compare/v1.10.0...v1.11.0) (2024-12-11)
2+
3+
4+
### Features
5+
6+
* product links block ([#53](https://github.com/adobe-rnd/helix-commerce-api/issues/53)) ([6544924](https://github.com/adobe-rnd/helix-commerce-api/commit/65449243b95dd1b0e7a7e3111e9114a5a4bcfe19))
7+
8+
# [1.10.0](https://github.com/adobe-rnd/helix-commerce-api/compare/v1.9.0...v1.10.0) (2024-12-10)
9+
10+
11+
### Features
12+
13+
* config validation and post ([#60](https://github.com/adobe-rnd/helix-commerce-api/issues/60)) ([9cd6cfc](https://github.com/adobe-rnd/helix-commerce-api/commit/9cd6cfc6e55e2de48dd12d3b6a1bf6d123d6d7f7))
14+
115
# [1.9.0](https://github.com/adobe-rnd/helix-commerce-api/compare/v1.8.1...v1.9.0) (2024-12-10)
216

317

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "helix-commerce-api",
3-
"version": "1.9.0",
3+
"version": "1.11.0",
44
"private": true,
55
"description": "API for markup content and a commerce graphql commerce proxy",
66
"main": "src/index.js",

src/content/adobe-commerce.js

+70-9
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
import { errorResponse, errorWithResponse, ffetch } from '../utils/http.js';
1414
import getProductQuery, { adapter as productAdapter } from './queries/cs-product.js';
1515
import getVariantsQuery, { adapter as variantsAdapter } from './queries/cs-variants.js';
16-
import getProductSKUQuery from './queries/core-product-sku.js';
16+
import getProductSKUQueryCore from './queries/core-product-sku.js';
17+
import getProductSKUQueryCS from './queries/cs-product-sku.js';
1718
import htmlTemplateFromContext from '../templates/html/index.js';
1819

1920
/**
@@ -22,7 +23,11 @@ import htmlTemplateFromContext from '../templates/html/index.js';
2223
*/
2324
async function fetchProduct(sku, config) {
2425
const { catalogEndpoint = 'https://catalog-service.adobe.io/graphql' } = config;
25-
const query = getProductQuery({ sku, imageRoles: config.imageRoles });
26+
const query = getProductQuery({
27+
sku,
28+
imageRoles: config.imageRoles,
29+
linkTypes: config.linkTypes,
30+
});
2631
console.debug(query);
2732

2833
const resp = await ffetch(`${catalogEndpoint}?query=${encodeURIComponent(query)}&view=${config.storeViewCode}`, {
@@ -111,8 +116,53 @@ async function fetchVariants(sku, config) {
111116
* @param {string} urlkey
112117
* @param {Config} config
113118
*/
114-
async function lookupProductSKU(urlkey, config) {
115-
const query = getProductSKUQuery({ urlkey });
119+
async function lookupProductSKUCS(urlkey, config) {
120+
const { catalogEndpoint = 'https://catalog-service.adobe.io/graphql' } = config;
121+
const query = getProductSKUQueryCS({ urlkey });
122+
console.debug(query);
123+
124+
const resp = await ffetch(`${catalogEndpoint}?query=${encodeURIComponent(query)}`, {
125+
headers: {
126+
origin: config.origin ?? 'https://api.adobecommerce.live',
127+
'x-api-key': config.apiKey,
128+
'Magento-Environment-Id': config.magentoEnvironmentId,
129+
'Magento-Website-Code': config.magentoWebsiteCode,
130+
'Magento-Store-View-Code': config.storeViewCode,
131+
'Magento-Store-Code': config.storeCode,
132+
...config.headers,
133+
},
134+
// don't disable cache, since it's unlikely to change
135+
});
136+
if (!resp.ok) {
137+
console.warn('failed to fetch product sku (cs): ', resp.status, resp.statusText);
138+
try {
139+
console.info('body: ', await resp.text());
140+
} catch { /* noop */ }
141+
throw errorWithResponse(resp.status, 'failed to fetch product sku (cs)');
142+
}
143+
144+
try {
145+
const json = await resp.json();
146+
const [product] = json?.data?.productSearch.items ?? [];
147+
if (!product?.product?.sku) {
148+
throw errorWithResponse(404, 'could not find product sku (cs)', json.errors);
149+
}
150+
return product.product.sku;
151+
} catch (e) {
152+
console.error('failed to parse product sku (cs): ', e);
153+
if (e.response) {
154+
throw errorWithResponse(e.response.status, e.message);
155+
}
156+
throw errorWithResponse(500, 'failed to parse product sku response (cs)');
157+
}
158+
}
159+
160+
/**
161+
* @param {string} urlkey
162+
* @param {Config} config
163+
*/
164+
async function lookupProductSKUCore(urlkey, config) {
165+
const query = getProductSKUQueryCore({ urlkey });
116166
if (!config.coreEndpoint) {
117167
throw errorResponse(400, 'missing coreEndpoint');
118168
}
@@ -127,27 +177,38 @@ async function lookupProductSKU(urlkey, config) {
127177
// don't disable cache, since it's unlikely to change
128178
});
129179
if (!resp.ok) {
130-
console.warn('failed to fetch product sku: ', resp.status, resp.statusText);
180+
console.warn('failed to fetch product sku (core): ', resp.status, resp.statusText);
131181
try {
132182
console.info('body: ', await resp.text());
133183
} catch { /* noop */ }
134-
throw errorWithResponse(resp.status, 'failed to fetch product sku');
184+
throw errorWithResponse(resp.status, 'failed to fetch product sku (core)');
135185
}
136186

137187
try {
138188
const json = await resp.json();
139189
const [product] = json?.data?.products?.items ?? [];
140190
if (!product?.sku) {
141-
throw errorWithResponse(404, 'could not find product sku', json.errors);
191+
throw errorWithResponse(404, 'could not find product sku (core)', json.errors);
142192
}
143193
return product.sku;
144194
} catch (e) {
145-
console.error('failed to parse product sku: ', e);
195+
console.error('failed to parse product sku (core): ', e);
146196
if (e.response) {
147197
throw errorWithResponse(e.response.status, e.message);
148198
}
149-
throw errorWithResponse(500, 'failed to parse product sku response');
199+
throw errorWithResponse(500, 'failed to parse product sku response (core)');
200+
}
201+
}
202+
203+
/**
204+
* @param {string} urlkey
205+
* @param {Config} config
206+
*/
207+
function lookupProductSKU(urlkey, config) {
208+
if (config.liveSearchEnabled) {
209+
return lookupProductSKUCS(urlkey, config);
150210
}
211+
return lookupProductSKUCore(urlkey, config);
151212
}
152213

153214
/**

src/content/queries/cs-product-sku.js

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* Copyright 2024 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import { gql } from '../../utils/product.js';
14+
15+
/**
16+
* @param {{ urlkey: string; }} param0
17+
*/
18+
// @ts-ignore
19+
export default ({ urlkey }) => gql`{
20+
productSearch (
21+
phrase:""
22+
page_size: 1
23+
filter: {
24+
attribute: "url_key"
25+
eq: "${urlkey}"
26+
}
27+
) {
28+
items {
29+
product {
30+
sku
31+
uid
32+
}
33+
}
34+
}
35+
}`;

src/content/queries/cs-product.js

+95-8
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,26 @@
1313
import { forceImagesHTTPS } from '../../utils/http.js';
1414
import { gql, parseRating, parseSpecialToDate } from '../../utils/product.js';
1515

16+
function extractMinMaxPrice(data) {
17+
let minPrice = data.priceRange?.minimum ?? data.price;
18+
let maxPrice = data.priceRange?.maximum ?? data.price;
19+
20+
if (minPrice == null) {
21+
minPrice = maxPrice;
22+
} else if (maxPrice == null) {
23+
maxPrice = minPrice;
24+
}
25+
return { minPrice, maxPrice };
26+
}
27+
1628
/**
1729
* @param {Config} config
1830
* @param {any} productData
1931
* @returns {Product}
2032
*/
2133
export const adapter = (config, productData) => {
22-
let minPrice = productData.priceRange?.minimum ?? productData.price;
23-
let maxPrice = productData.priceRange?.maximum ?? productData.price;
34+
const { minPrice, maxPrice } = extractMinMaxPrice(productData);
2435

25-
if (minPrice == null) {
26-
minPrice = maxPrice;
27-
} else if (maxPrice == null) {
28-
maxPrice = minPrice;
29-
}
3036
/** @type {Product} */
3137
const product = {
3238
sku: productData.sku,
@@ -41,6 +47,28 @@ export const adapter = (config, productData) => {
4147
addToCartAllowed: productData.addToCartAllowed,
4248
inStock: productData.inStock,
4349
externalId: productData.externalId,
50+
links: (productData.links ?? []).map((l) => {
51+
const { minPrice: lMinPrice, maxPrice: lMaxPrice } = extractMinMaxPrice(l.product);
52+
return {
53+
sku: l.product.sku,
54+
urlKey: l.product.urlKey,
55+
types: l.linkTypes,
56+
prices: {
57+
regular: {
58+
amount: lMinPrice.regular.amount.value,
59+
currency: lMinPrice.regular.amount.currency,
60+
maximumAmount: lMaxPrice.regular.amount.value,
61+
minimumAmount: lMinPrice.regular.amount.value,
62+
},
63+
final: {
64+
amount: lMinPrice.final.amount.value,
65+
currency: lMinPrice.final.amount.currency,
66+
maximumAmount: lMaxPrice.final.amount.value,
67+
minimumAmount: lMinPrice.final.amount.value,
68+
},
69+
},
70+
};
71+
}),
4472
images: forceImagesHTTPS(productData.images) ?? [],
4573
attributes: productData.attributes ?? [],
4674
attributeMap: Object.fromEntries((productData.attributes ?? [])
@@ -115,9 +143,10 @@ export const adapter = (config, productData) => {
115143
* @param {{
116144
* sku: string;
117145
* imageRoles?: string[];
146+
* linkTypes?: string[];
118147
* }} opts
119148
*/
120-
export default ({ sku, imageRoles = [] }) => gql`{
149+
export default ({ sku, imageRoles = [], linkTypes = [] }) => gql`{
121150
products(
122151
skus: ["${sku}"]
123152
) {
@@ -139,6 +168,64 @@ export default ({ sku, imageRoles = [] }) => gql`{
139168
url
140169
label
141170
}
171+
links(linkTypes: [${linkTypes.map((s) => `"${s}"`).join(',')}]) {
172+
product {
173+
sku
174+
urlKey
175+
... on SimpleProductView {
176+
price {
177+
final {
178+
amount {
179+
value
180+
currency
181+
}
182+
}
183+
regular {
184+
amount {
185+
value
186+
currency
187+
}
188+
}
189+
roles
190+
}
191+
}
192+
... on ComplexProductView {
193+
priceRange {
194+
maximum {
195+
final {
196+
amount {
197+
value
198+
currency
199+
}
200+
}
201+
regular {
202+
amount {
203+
value
204+
currency
205+
}
206+
}
207+
roles
208+
}
209+
minimum {
210+
final {
211+
amount {
212+
value
213+
currency
214+
}
215+
}
216+
regular {
217+
amount {
218+
value
219+
currency
220+
}
221+
}
222+
roles
223+
}
224+
}
225+
}
226+
}
227+
linkTypes
228+
}
142229
attributes(roles: ["visible_in_pdp"]) {
143230
name
144231
label

0 commit comments

Comments
 (0)