Skip to content

Commit d1c1bfa

Browse files
committed
Merge branch 'main' of github.com:adobe-rnd/helix-commerce-api into additional-data
2 parents a23662d + d3cc60b commit d1c1bfa

File tree

11 files changed

+371
-52
lines changed

11 files changed

+371
-52
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
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+
18
# [1.10.0](https://github.com/adobe-rnd/helix-commerce-api/compare/v1.9.0...v1.10.0) (2024-12-10)
29

310

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.10.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 ?? [])
@@ -116,9 +144,10 @@ export const adapter = (config, productData) => {
116144
* @param {{
117145
* sku: string;
118146
* imageRoles?: string[];
147+
* linkTypes?: string[];
119148
* }} opts
120149
*/
121-
export default ({ sku, imageRoles = [] }) => gql`{
150+
export default ({ sku, imageRoles = [], linkTypes = [] }) => gql`{
122151
products(
123152
skus: ["${sku}"]
124153
) {
@@ -140,6 +169,64 @@ export default ({ sku, imageRoles = [] }) => gql`{
140169
url
141170
label
142171
}
172+
links(linkTypes: [${linkTypes.map((s) => `"${s}"`).join(',')}]) {
173+
product {
174+
sku
175+
urlKey
176+
... on SimpleProductView {
177+
price {
178+
final {
179+
amount {
180+
value
181+
currency
182+
}
183+
}
184+
regular {
185+
amount {
186+
value
187+
currency
188+
}
189+
}
190+
roles
191+
}
192+
}
193+
... on ComplexProductView {
194+
priceRange {
195+
maximum {
196+
final {
197+
amount {
198+
value
199+
currency
200+
}
201+
}
202+
regular {
203+
amount {
204+
value
205+
currency
206+
}
207+
}
208+
roles
209+
}
210+
minimum {
211+
final {
212+
amount {
213+
value
214+
currency
215+
}
216+
}
217+
regular {
218+
amount {
219+
value
220+
currency
221+
}
222+
}
223+
roles
224+
}
225+
}
226+
}
227+
}
228+
linkTypes
229+
}
143230
attributes(roles: ["visible_in_pdp"]) {
144231
name
145232
label

0 commit comments

Comments
 (0)