Skip to content

Commit

Permalink
Merge pull request #35 from adobe-rnd/variant-urls-and-overrides
Browse files Browse the repository at this point in the history
feat: fix offer urls and add variant overrides
  • Loading branch information
dylandepass authored Nov 4, 2024
2 parents 17f56cb + 1444e94 commit a13ace0
Show file tree
Hide file tree
Showing 23 changed files with 688 additions and 35 deletions.
3 changes: 3 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import { errorWithResponse } from './utils/http.js';
import { siteOverrides } from './overrides/index.js';

/**
* This function finds ordered matches between a list of patterns and a given path.
Expand Down Expand Up @@ -104,7 +105,9 @@ export async function resolveConfig(ctx, overrides = {}) {
org,
site,
route,
siteOverrides: siteOverrides[siteKey],
...overrides,
};

return resolved;
}
4 changes: 2 additions & 2 deletions src/content/adobe-commerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ async function fetchVariants(sku, config) {
try {
const json = await resp.json();
const { variants } = json?.data?.variants ?? {};
return variantsAdapter(variants);
return variantsAdapter(config, variants);
} catch (e) {
console.error('failed to parse variants: ', e);
throw errorWithResponse(500, 'failed to parse variants response');
Expand Down Expand Up @@ -173,7 +173,7 @@ export async function handle(ctx, config) {
fetchProduct(sku.toUpperCase(), config),
fetchVariants(sku.toUpperCase(), config),
]);
const html = HTML_TEMPLATE(product, variants);
const html = HTML_TEMPLATE(config, product, variants);
return new Response(html, {
status: 200,
headers: {
Expand Down
22 changes: 21 additions & 1 deletion src/content/handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* governing permissions and limitations under the License.
*/

import { matchConfigPath } from '../utils/product.js';
import { errorResponse } from '../utils/http.js';
import { handle as handleAdobeCommerce } from './adobe-commerce.js';
import { handle as handleHelixCommerce } from './helix-commerce.js';
Expand All @@ -30,9 +31,28 @@ export default async function contentHandler(ctx, config) {
return errorResponse(400, 'invalid config for tenant site (missing pageType)');
}

const { pathname } = ctx.url;
const productPath = pathname.includes('/content/product')
? pathname.split('/content/product')[1]
: null;

if (productPath) {
const matchedPath = matchConfigPath(config, productPath);
const matchedPathConfig = config.confMap && config.confMap[matchedPath]
? config.confMap[matchedPath]
: null;
if (matchedPathConfig) {
config.matchedPath = matchedPath;
config.matchedPathConfig = matchedPathConfig;
} else {
return errorResponse(400, 'No matching configuration for product path');
}
} else {
return errorResponse(400, 'invalid product path');
}

if (config.catalogSource === 'helix') {
return handleHelixCommerce(ctx, config);
}

return handleAdobeCommerce(ctx, config);
}
2 changes: 1 addition & 1 deletion src/content/helix-commerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export async function handle(ctx, config) {
}

const product = await fetchProduct(ctx, config, sku);
const html = HTML_TEMPLATE(product, product.variants);
const html = HTML_TEMPLATE(config, product, product.variants);
return new Response(html, {
status: 200,
headers: {
Expand Down
4 changes: 2 additions & 2 deletions src/content/queries/cs-product.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
* governing permissions and limitations under the License.
*/

import { forceImagesHTTPS } from '../../utils/http.js';
import { gql } from '../../utils/product.js';

/**
Expand All @@ -25,7 +26,6 @@ export const adapter = (productData) => {
} else if (maxPrice == null) {
maxPrice = minPrice;
}

/** @type {Product} */
const product = {
sku: productData.sku,
Expand All @@ -40,7 +40,7 @@ export const adapter = (productData) => {
addToCartAllowed: productData.addToCartAllowed,
inStock: productData.inStock,
externalId: productData.externalId,
images: productData.images ?? [],
images: forceImagesHTTPS(productData.images) ?? [],
attributes: productData.attributes ?? [],
options: (productData.options ?? []).map((option) => ({
id: option.id,
Expand Down
13 changes: 11 additions & 2 deletions src/content/queries/cs-variants.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@
* governing permissions and limitations under the License.
*/

import { forceImagesHTTPS } from '../../utils/http.js';
import { gql } from '../../utils/product.js';

/**
* @param {any} variants
* @returns {Variant[]}
*/
export const adapter = (variants) => variants.map(({ selections, product }) => {
export const adapter = (config, variants) => variants.map(({ selections, product }) => {
const minPrice = product.priceRange?.minimum ?? product.price;
const maxPrice = product.priceRange?.maximum ?? product.price;

Expand All @@ -27,8 +28,9 @@ export const adapter = (variants) => variants.map(({ selections, product }) => {
description: product.description,
url: product.url,
inStock: product.inStock,
images: product.images ?? [],
images: forceImagesHTTPS(product.images) ?? [],
attributes: product.attributes ?? [],
externalId: product.externalId,
prices: {
regular: {
// TODO: determine whether to use min or max
Expand All @@ -47,6 +49,12 @@ export const adapter = (variants) => variants.map(({ selections, product }) => {
},
selections: selections ?? [],
};
if (config.attributeOverrides?.variant) {
Object.entries(config.attributeOverrides.variant).forEach(([key, value]) => {
variant[key] = product.attributes?.find((attr) => attr.name === value)?.value;
});
}

return variant;
});

Expand All @@ -63,6 +71,7 @@ export default (sku) => gql`
name
sku
inStock
externalId
images {
url
label
Expand Down
31 changes: 31 additions & 0 deletions src/overrides/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright 2024 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*/

export const siteOverrides = {
'thepixel--bul-eds': {
constructProductUrl: (config, product, variant) => {
const { host, matchedPath } = config;
const productPath = matchedPath
.replace('{{urlkey}}', product.urlKey)
.replace('{{sku}}', encodeURIComponent(product.sku.toLowerCase()));

const productUrl = `${host}${productPath}`;

if (variant) {
const options = variant.selections.map((selection) => atob(selection)).join(',').replace(/configurable\//g, '').replace(/\//g, '-');
return `${productUrl}?pid=${variant.externalId}&o=${btoa(options)}`;
}

return productUrl;
},
},
};
13 changes: 8 additions & 5 deletions src/templates/html.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const priceRange = (min, max) => (min !== max ? ` (${min} - ${max})` : '');
* @returns {string}
*/
export const renderDocumentMetaTags = (product) => /* html */ `
<meta charset="UTF-8">
<title>${product.metaTitle || product.name}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
${metaProperty('description', product.metaDescription)}
Expand Down Expand Up @@ -111,20 +112,22 @@ export const renderHelixDependencies = () => /* html */ `
* @param {Variant[]} variants
* @returns {string}
*/
export const renderJSONLD = (product, variants) => /* html */ `
export const renderJSONLD = (config, product, variants) => /* html */ `
<script type="application/ld+json">
${JSON_LD_TEMPLATE(product, variants)}
${JSON_LD_TEMPLATE(config, product, variants)}
</script>
`;

/**
* Create the head tags
* @param {Config} config
* @param {Product} product
* @param {Variant[]} variants
* @param {Object} [options]
* @returns {string}
*/
export const renderHead = (
config,
product,
variants,
{
Expand All @@ -145,7 +148,7 @@ export const renderHead = (
${twitterMetaTags(product, image)}
${commerceMetaTags(product)}
${helixDependencies()}
${JSONLD(product, variants)}
${JSONLD(config, product, variants)}
</head>
`;
};
Expand Down Expand Up @@ -302,7 +305,7 @@ const renderProductVariantsAttributes = (variants) => /* html */ `
* @param {Variant[]} variants
* @returns {string}
*/
export default (product, variants) => {
export default (config, product, variants) => {
const {
name,
description,
Expand All @@ -314,7 +317,7 @@ export default (product, variants) => {
return /* html */`
<!DOCTYPE html>
<html>
${renderHead(product, variants)}
${renderHead(config, product, variants)}
<body>
<header></header>
<main>
Expand Down
38 changes: 24 additions & 14 deletions src/templates/json-ld.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,16 @@
* governing permissions and limitations under the License.
*/

import { findProductImage, pruneUndefined } from '../utils/product.js';
import { constructProductUrl as constructProductUrlBase, findProductImage, pruneUndefined } from '../utils/product.js';

/**
* @param {Product} product
* @param {Variant[]} variants
* @returns {string}
*/
export default (product, variants) => {
export default (config, product, variants) => {
const {
sku,
url,
name,
metaDescription,
images,
Expand All @@ -31,12 +30,15 @@ export default (product, variants) => {
prices,
} = product;

const constructProductUrl = config.siteOverrides?.constructProductUrl ?? constructProductUrlBase;

const productUrl = constructProductUrl(config, product);
const image = images?.[0]?.url ?? findProductImage(product, variants)?.url;
const brandName = attributes?.find((attr) => attr.name === 'brand')?.value;
return JSON.stringify(pruneUndefined({
'@context': 'http://schema.org',
'@type': 'Product',
'@id': url,
'@id': productUrl,
name,
sku,
description: metaDescription,
Expand All @@ -46,22 +48,30 @@ export default (product, variants) => {
prices ? ({
'@type': 'Offer',
sku,
url,
url: productUrl,
image,
availability: inStock ? 'InStock' : 'OutOfStock',
price: prices?.final?.amount,
priceCurrency: prices?.final?.currency,
}) : undefined,
...variants.map((v) => ({
'@type': 'Offer',
sku: v.sku,
url: v.url,
image: v.images?.[0]?.url ?? image,
availability: v.inStock ? 'InStock' : 'OutOfStock',
price: v.prices?.final?.amount,
priceCurrency: v.prices?.final?.currency,
...variants.map((v) => {
const offerUrl = constructProductUrl(config, product, v);
const offer = {
'@type': 'Offer',
sku: v.sku,
url: offerUrl,
image: v.images?.[0]?.url ?? image,
availability: v.inStock ? 'InStock' : 'OutOfStock',
price: v.prices?.final?.amount,
priceCurrency: v.prices?.final?.currency,
};

if (v.gtin) {
offer.gtin = v.gtin;
}

})).filter(Boolean),
return offer;
}).filter(Boolean),
],
...(brandName
? {
Expand Down
14 changes: 14 additions & 0 deletions src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ declare global {
* { pathPattern => Config }
*/
export type ConfigMap = Record<string, Config>;

export interface AttributeOverrides {
variant: {
[key: string]: string;
};
}

/**
* Resolved config object
Expand All @@ -27,6 +33,12 @@ declare global {
confMap: ConfigMap;
params: Record<string, string>;
headers: Record<string, string>;
host: string;
offerVariantURLTemplate: string;
matchedPath: string;
matchedPathConfig: Config;
attributeOverrides: AttributeOverrides;
siteOverrides: Record<string, Record<string, unknown>>;
}

export interface Env {
Expand Down Expand Up @@ -86,6 +98,8 @@ declare global {
prices: Pick<Prices, 'regular' | 'final'>;
selections: string[];
attributes: Attribute[];
externalId: string;
gtin?: string;
}

interface Image {
Expand Down
7 changes: 7 additions & 0 deletions src/utils/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,10 @@ export function errorWithResponse(status, xError, body = '') {
const error = new ResponseError(xError, response);
return error;
}

export function forceImagesHTTPS(images) {
return images?.map((img) => ({
...img,
url: img.url.replace('http://', 'https://'),
}));
}
Loading

0 comments on commit a13ace0

Please sign in to comment.