Fetch product images, standardize API fetch using Spree SDK

This commit is contained in:
tniezg
2021-07-27 17:42:05 +02:00
parent c90fa7abf2
commit 14e7a4fe08
17 changed files with 486 additions and 216 deletions

View File

@@ -1,4 +1,4 @@
import type { APIProvider, CommerceAPI, CommerceAPIConfig } from '@commerce/api'
import type { APIProvider, CommerceAPIConfig } from '@commerce/api'
import { getCommerceApi as commerceApi } from '@commerce/api'
import createApiFetch from './utils/create-api-fetch'

View File

@@ -1,78 +1,142 @@
import { Product } from '@commerce/types/product'
import { GetAllProductsOperation } from '@commerce/types/product'
import type {
Product,
ProductOption,
ProductOptionValues,
ProductPrice,
ProductVariant,
} from '@commerce/types/product'
import type { GetAllProductsOperation } from '@commerce/types/product'
import type { OperationContext } from '@commerce/api/operations'
import type { LocalConfig, Provider, SpreeApiProvider } from '../index'
import type { IProducts } from '@spree/storefront-api-v2-sdk/types/interfaces/Product'
// import data from '../../../local/data.json'
import { RelationType } from '@spree/storefront-api-v2-sdk/types/interfaces/Relationships'
import type { SpreeApiConfig, SpreeApiProvider } from '../index'
import type { SpreeSdkVariables } from 'framework/spree/types'
import { findIncluded, findIncludedOfType } from 'framework/spree/utils/jsonApi'
import getMediaGallery from 'framework/spree/utils/getMediaGallery'
import createGetAbsoluteImageUrl from 'framework/spree/utils/createGetAbsoluteImageUrl'
import { requireConfigValue } from 'framework/spree/isomorphicConfig'
import SpreeResponseContentError from 'framework/spree/errors/SpreeResponseContentError'
import expandOptions from 'framework/spree/utils/expandOptions'
export default function getAllProductsOperation({
commerce,
}: OperationContext<SpreeApiProvider>) {
async function getAllProducts<T extends GetAllProductsOperation>({
query = 'products.list',
variables = { first: 10 },
variables: getAllProductsVariables = {},
config: userConfig,
}: {
query?: string
variables?: T['variables']
config?: Partial<LocalConfig>
config?: Partial<SpreeApiConfig>
} = {}): Promise<{ products: Product[] | any[] }> {
const config = commerce.getConfig(userConfig)
const { fetch: apiFetch /*, locale*/ } = config
const first = variables.first // How many products to fetch.
console.log(
'sdfuasdufahsdf variables = ',
variables,
'query = ',
query,
'config = ',
config
console.info(
'getAllProducts called. Configuration: ',
'getAllProductsVariables: ',
getAllProductsVariables,
'config: ',
userConfig
)
console.log('sdfasdg')
const { data } = await apiFetch<IProducts>(
query,
{ variables }
// {
// ...(locale && {}),
// }
)
console.log('asuidfhasdf', data)
// return {
// products: data.products.edges.map(({ node }) =>
// normalizeProduct(node as ShopifyProduct)
// ),
// }
const normalizedProducts: Product[] = data.data.map((spreeProduct) => {
return {
id: spreeProduct.id,
name: spreeProduct.attributes.name,
description: spreeProduct.attributes.description,
images: [],
variants: [],
options: [],
price: {
value: 10,
currencyCode: 'USD',
retailPrice: 8,
salePrice: 7,
listPrice: 6,
extendedSalePrice: 2,
extendedListPrice: 1,
const first = getAllProductsVariables.first
const variables: SpreeSdkVariables = {
methodPath: 'products.list',
arguments: [
{
include: 'variants,images,option_types,variants.option_values',
per_page: first,
},
}
})
return {
// products: data.products,
// TODO: Return Spree products.
products: normalizedProducts,
],
}
const config = commerce.getConfig(userConfig)
const { fetch: apiFetch } = config // TODO: Send config.locale to Spree.
const { data: spreeSuccessResponse } = await apiFetch<IProducts>(
'__UNUSED__',
{ variables }
)
const normalizedProducts: Product[] = spreeSuccessResponse.data.map(
(spreeProduct) => {
const spreeImageRecords = findIncludedOfType(
spreeSuccessResponse,
spreeProduct,
'images'
)
const images = getMediaGallery(
spreeImageRecords,
createGetAbsoluteImageUrl(requireConfigValue('spreeImageHost'))
)
const price: ProductPrice = {
value: parseFloat(spreeProduct.attributes.price),
currencyCode: spreeProduct.attributes.currency,
}
// TODO: Add sku to product object equal to master SKU from Spree.
// Currently, the Spree API doesn't return it.
const hasNonMasterVariants =
(spreeProduct.relationships.variants.data as RelationType[]).length >
0
let variants: ProductVariant[]
let options: ProductOption[] = []
if (hasNonMasterVariants) {
const spreeVariantRecords = findIncludedOfType(
spreeSuccessResponse,
spreeProduct,
'variants'
)
variants = spreeVariantRecords.map((spreeVariantRecord) => {
const spreeOptionValues = findIncludedOfType(
spreeSuccessResponse,
spreeVariantRecord,
'option_values'
)
let variantOptions: ProductOption[] = []
// Only include options which are used by variants.
spreeOptionValues.forEach((spreeOptionValue) => {
variantOptions = expandOptions(
spreeSuccessResponse,
spreeOptionValue,
variantOptions
)
options = expandOptions(
spreeSuccessResponse,
spreeOptionValue,
options
)
})
return {
id: spreeVariantRecord.id,
options: variantOptions,
}
})
} else {
variants = []
}
return {
id: spreeProduct.id,
name: spreeProduct.attributes.name,
description: spreeProduct.attributes.description,
images,
variants,
options,
price,
}
}
)
return { products: normalizedProducts }
}
return getAllProducts

View File

@@ -1,7 +1,3 @@
// import { FetcherError } from '@commerce/utils/errors'
// import type { GraphQLFetcher } from '@commerce/api'
// import type { BigcommerceConfig } from '../index'
import { GraphQLFetcher, GraphQLFetcherResult } from '@commerce/api'
import { SpreeApiConfig } from '..'
import { errors, makeClient } from '@spree/storefront-api-v2-sdk'
@@ -9,81 +5,54 @@ import { requireConfigValue } from 'framework/spree/isomorphicConfig'
import convertSpreeErrorToGraphQlError from 'framework/spree/utils/convertSpreeErrorToGraphQlError'
import type { ResultResponse } from '@spree/storefront-api-v2-sdk/types/interfaces/ResultResponse'
import type {
JsonApiDocument,
JsonApiListResponse,
JsonApiResponse,
} from '@spree/storefront-api-v2-sdk/types/interfaces/JsonApi'
// import fetch from './fetch'
// const fetchGraphqlApi: (getConfig: () => BigcommerceConfig) => GraphQLFetcher =
// (getConfig) =>
// async (query: string, { variables, preview } = {}, fetchOptions) => {
// // log.warn(query)
// const config = getConfig()
// const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
// ...fetchOptions,
// method: 'POST',
// headers: {
// Authorization: `Bearer ${config.apiToken}`,
// ...fetchOptions?.headers,
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// query,
// variables,
// }),
// })
// const json = await res.json()
// if (json.errors) {
// throw new FetcherError({
// errors: json.errors ?? [{ message: 'Failed to fetch Bigcommerce API' }],
// status: res.status,
// })
// }
// return { data: json.data, res }
// }
// export default fetchGraphqlApi
import getSpreeSdkMethodFromEndpointPath from 'framework/spree/utils/getSpreeSdkMethodFromEndpointPath'
import { SpreeSdkVariables } from 'framework/spree/types'
import SpreeSdkMethodFromEndpointPathError from 'framework/spree/errors/SpreeSdkMethodFromEndpointPathError'
const createApiFetch: (
getConfig: () => SpreeApiConfig
) => GraphQLFetcher<
GraphQLFetcherResult<JsonApiDocument | JsonApiListResponse>
> = (getConfig) => {
) => GraphQLFetcher<GraphQLFetcherResult<any>, SpreeSdkVariables> = (
getConfig
) => {
const client = makeClient({ host: requireConfigValue('spreeApiHost') })
// FIXME: Allow Spree SDK to use fetch instead of axios.
return async (query, queryData = {}, fetchOptions = {}) => {
const url = query
console.log('ydsfgasgdfagsdf', url)
return async (url, queryData = {}, fetchOptions = {}) => {
console.log(
'apiFetch called. query = ',
url,
'url = ',
queryData,
'fetchOptions = ',
fetchOptions
)
const { variables } = queryData
let prev = null // FIXME:
const clientEndpointMethod = url
.split('.')
.reduce((clientNode: any, pathPart) => {
prev = clientNode
//FIXME: use actual type instead of any.
// TODO: Fix clientNode type
return clientNode[pathPart]
}, client)
.bind(prev)
console.log('aisdfuiuashdf', clientEndpointMethod)
if (!variables) {
throw new SpreeSdkMethodFromEndpointPathError(
`Required SpreeSdkVariables not provided.`
)
}
const storeResponse: ResultResponse<JsonApiDocument | JsonApiListResponse> =
await clientEndpointMethod() // FIXME: Not the best to use variables here as it's type is any.
// await clientEndpointMethod(...variables.args) // FIXME: Not the best to use variables here as it's type is any.
console.log('87868767868', storeResponse)
const storeResponse: ResultResponse<JsonApiResponse | JsonApiListResponse> =
await getSpreeSdkMethodFromEndpointPath(
client,
variables.methodPath
)(...variables.arguments)
if (storeResponse.success()) {
return {
data: storeResponse.success(),
res: storeResponse as any, //FIXME: MUST BE FETCH RESPONSE
res: storeResponse as any, //FIXME: MUST BE fetch() RESPONSE instead of axios.
}
}
// FIXME: Allow Spree SDK to use fetch instead of axios
// (https://github.com/spree/spree-storefront-api-v2-js-sdk/issues/189)
const storeResponseError = storeResponse.fail()
if (storeResponseError instanceof errors.SpreeError) {
@@ -91,64 +60,7 @@ const createApiFetch: (
}
throw storeResponseError
// throw getError(
// [
// {
// message: `${err} \n Most likely related to an unexpected output. e.g the store might be protected with password or not available.`,
// },
// ],
// 500
// )
// console.log('jsdkfhjasdf', getConfig())
// // await
// return {
// data: [],
// res: ,
// }
}
}
export default createApiFetch
// LOCAL
// fetch<Data = any, Variables = any>(
// query: string,
// queryData?: CommerceAPIFetchOptions<Variables>,
// fetchOptions?: RequestInit
// ): Promise<GraphQLFetcherResult<Data>>
// import { FetcherError } from '@commerce/utils/errors'
// import type { GraphQLFetcher } from '@commerce/api'
// import type { LocalConfig } from '../index'
// import fetch from './fetch'
// const fetchGraphqlApi: (getConfig: () => LocalConfig) => GraphQLFetcher =
// (getConfig) =>
// async (query: string, { variables, preview } = {}, fetchOptions) => {
// const config = getConfig()
// const res = await fetch(config.commerceUrl, {
// ...fetchOptions,
// method: 'POST',
// headers: {
// ...fetchOptions?.headers,
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// query,
// variables,
// }),
// })
// const json = await res.json()
// if (json.errors) {
// throw new FetcherError({
// errors: json.errors ?? [{ message: 'Failed to fetch for API' }],
// status: res.status,
// })
// }
// return { data: json.data, res }
// }
// export default fetchGraphqlApi