diff --git a/framework/ordercloud/api/operations/get-all-product-paths.ts b/framework/ordercloud/api/operations/get-all-product-paths.ts index aa5c8d052..e21c2d195 100644 --- a/framework/ordercloud/api/operations/get-all-product-paths.ts +++ b/framework/ordercloud/api/operations/get-all-product-paths.ts @@ -1,15 +1,33 @@ -import data from '../../data.json' +import type { OrdercloudConfig, Provider } from '../index' +import type { OperationContext } from '@commerce/api/operations' +import type { GetAllProductPathsOperation } from '@commerce/types/product' +import { RawProduct } from '@framework/types/product' export type GetAllProductPathsResult = { products: Array<{ path: string }> } -export default function getAllProductPathsOperation() { - function getAllProductPaths(): Promise { - return Promise.resolve({ - products: [] - // products: data.products.map(({ path }) => ({ path })), - }) +export default function getAllProductPathsOperation({ + commerce, +}: OperationContext) { + async function getAllProductPaths({ + config, + }: { + config?: Partial + } = {}): Promise { + // Get fetch from the config + const { fetch } = commerce.getConfig(config) + + // Get all products + const rawProducts: RawProduct[] = await fetch<{ Items: RawProduct[] }>( + 'GET', + '/products' + ).then((response) => response.Items) + + return { + // Match a path for every product retrieved + products: rawProducts.map((product) => ({ path: `/${product.ID}` })), + } } return getAllProductPaths diff --git a/framework/ordercloud/api/operations/get-all-products.ts b/framework/ordercloud/api/operations/get-all-products.ts index 7dc6f71ff..f50f38556 100644 --- a/framework/ordercloud/api/operations/get-all-products.ts +++ b/framework/ordercloud/api/operations/get-all-products.ts @@ -1,4 +1,3 @@ -import type { Product } from '@commerce/types/product' import type { GetAllProductsOperation } from '@commerce/types/product' import type { OperationContext } from '@commerce/api/operations' import type { RawProduct } from '@framework/types/product' @@ -16,15 +15,18 @@ export default function getAllProductsOperation({ variables?: T['variables'] config?: Partial preview?: boolean - } = {}): Promise<{ products: Product[] }> { + } = {}): Promise { + // Get fetch from the config const { fetch } = commerce.getConfig(config) + // Get all products const rawProducts: RawProduct[] = await fetch<{ Items: RawProduct[] }>( 'GET', '/products' ).then((response) => response.Items) return { + // Normalize products to commerce schema products: rawProducts.map(normalizeProduct), } } diff --git a/framework/ordercloud/api/operations/get-product.ts b/framework/ordercloud/api/operations/get-product.ts index 0cfc57122..e51985703 100644 --- a/framework/ordercloud/api/operations/get-product.ts +++ b/framework/ordercloud/api/operations/get-product.ts @@ -1,6 +1,5 @@ import type { OperationContext } from '@commerce/api/operations' import type { RawProduct } from '@framework/types/product' -import type { Product } from '@commerce/types/product' import type { GetProductOperation } from '@commerce/types/product' import type { OrdercloudConfig, Provider } from '../index' @@ -17,15 +16,18 @@ export default function getProductOperation({ variables?: T['variables'] config?: Partial preview?: boolean - } = {}): Promise<{ product: Product }> { + } = {}): Promise { + // Get fetch from the config const { fetch } = commerce.getConfig(config) + // Get a single product const rawProduct: RawProduct = await fetch( 'GET', `/products/${variables?.slug}` ) return { + // Normalize product to commerce schema product: normalizeProduct(rawProduct), } } diff --git a/framework/ordercloud/api/utils/fetch-rest.ts b/framework/ordercloud/api/utils/fetch-rest.ts index b48805dee..eefed5e72 100644 --- a/framework/ordercloud/api/utils/fetch-rest.ts +++ b/framework/ordercloud/api/utils/fetch-rest.ts @@ -1,5 +1,6 @@ -import { FetcherError } from '@commerce/utils/errors' import type { OrdercloudConfig } from '../index' + +import { FetcherError } from '@commerce/utils/errors' import fetch from './fetch' const fetchRestApi: ( @@ -18,8 +19,8 @@ const fetchRestApi: ( fetchOptions?: Record ) => { const { commerceUrl } = getConfig() - // Check if we have a token stored - if (!global.token) { + + async function getToken() { // If not, get a new one and store it const authResponse = await fetch(`${commerceUrl}/oauth/token`, { method: 'POST', @@ -48,32 +49,58 @@ const fetchRestApi: ( .then((response) => response.access_token) } - // Do the request with the correct headers - const dataResponse = await fetch(`${commerceUrl}/v1${resource}`, { - ...fetchOptions, - method, - headers: { - ...fetchOptions?.headers, - accept: 'application/json, text/plain, */*', - authorization: `Bearer ${global.token}`, - }, - body: body ? JSON.stringify(body) : undefined, - }) - - // If something failed getting the data response - if (!dataResponse.ok) { - // Get the body of it - const error = await dataResponse.json() - - // And return an error - throw new FetcherError({ - errors: [{ message: error.error_description.Code }], - status: error.error_description.HttpStatus, + async function fetchData(retries = 0): Promise { + // Do the request with the correct headers + const dataResponse = await fetch(`${commerceUrl}/v1${resource}`, { + ...fetchOptions, + method, + headers: { + ...fetchOptions?.headers, + accept: 'application/json, text/plain, */*', + authorization: `Bearer ${global.token}`, + }, + body: body ? JSON.stringify(body) : undefined, }) + + // If something failed getting the data response + if (!dataResponse.ok) { + // If token is expired + if (dataResponse.status === 401) { + // Reset it + global.token = null + + // Get a new one + await getToken() + + // And if retries left + if (retries < 2) { + // Refetch + return fetchData(retries + 1) + } + } + + // Get the body of it + const error = await dataResponse.json() + + // And return an error + throw new FetcherError({ + errors: [{ message: error.error_description.Code }], + status: error.error_description.HttpStatus, + }) + } + + // Return data response + return dataResponse.json() as Promise + } + + // Check if we have a token stored + if (!global.token) { + // If not, get a new one and store it + await getToken() } // Return the data and specify the expected type - return (await dataResponse.json()) as T + return fetchData() } export default fetchRestApi diff --git a/framework/ordercloud/types/product.ts b/framework/ordercloud/types/product.ts index 21155f29b..9c16f268c 100644 --- a/framework/ordercloud/types/product.ts +++ b/framework/ordercloud/types/product.ts @@ -23,5 +23,6 @@ export interface RawProduct { Images: { url: string }[] + Facets: Record } } diff --git a/framework/ordercloud/utils/product.ts b/framework/ordercloud/utils/product.ts index f316c31ff..64d7494d1 100644 --- a/framework/ordercloud/utils/product.ts +++ b/framework/ordercloud/utils/product.ts @@ -12,49 +12,32 @@ export function normalize(product: RawProduct): Product { value: product.xp.Price, currencyCode: product.xp.PriceCurrency, }, - // TODO: Implement this - variants: [ - { - id: 'unique', - options: [ - { - id: 'unique', - displayName: 'Model', - values: [ - { - label: 'Unique', - }, - ], - }, - ], - }, - ], - options: [ - { - id: 'option-color', - displayName: 'Color', - values: [ - { - label: 'color', - hexColors: ['#222'], - }, - ], - }, - { - id: 'option-size', - displayName: 'Size', - values: [ - { - label: 'S', - }, - { - label: 'M', - }, - { - label: 'L', - }, - ], - }, - ], + // Variants are not always present, in case they are not, return a single unique variant + variants: + product.VariantCount === 0 + ? [ + { + id: 'unique', + options: [ + { + id: 'unique', + displayName: 'Unique', + values: [{ label: 'Unique' }], + }, + ], + }, + ] + : [], + // Facets are not always present, just iterate them if they are + options: product.xp.Facets + ? Object.entries(product.xp.Facets).map(([key, values]) => ({ + id: key, + displayName: key, + __typename: 'MultipleChoiceOption', + values: values.map((value) => ({ + label: value, + })), + })) + : [], } }