From e4a99601f52492260ea3d8c2df2109daa4011d89 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Thu, 22 Oct 2020 13:25:44 -0500 Subject: [PATCH] Updated types, API, and make the signup work --- .../handlers/get-logged-in-customer.ts | 2 +- .../api/customers/handlers/signup.ts | 76 +++++++++---------- .../api/operations/get-all-product-paths.ts | 6 +- .../api/operations/get-all-products.ts | 2 +- lib/bigcommerce/api/operations/get-product.ts | 7 +- .../api/operations/get-site-info.ts | 7 +- lib/bigcommerce/api/operations/login.ts | 21 ++++- lib/bigcommerce/api/utils/concat-cookie.ts | 14 ++++ .../api/utils/fetch-graphql-api.ts | 28 +++---- lib/commerce/api/index.ts | 25 ++++-- 10 files changed, 111 insertions(+), 77 deletions(-) create mode 100644 lib/bigcommerce/api/utils/concat-cookie.ts diff --git a/lib/bigcommerce/api/customers/handlers/get-logged-in-customer.ts b/lib/bigcommerce/api/customers/handlers/get-logged-in-customer.ts index 99e9191f5..6ed97bec3 100644 --- a/lib/bigcommerce/api/customers/handlers/get-logged-in-customer.ts +++ b/lib/bigcommerce/api/customers/handlers/get-logged-in-customer.ts @@ -26,7 +26,7 @@ const getLoggedInCustomer: CustomersHandlers['getLoggedInCustomer'] = async ({ res, config, }) => { - const data = await config.fetch( + const { data } = await config.fetch( getLoggedInCustomerQuery ) const { customer } = data diff --git a/lib/bigcommerce/api/customers/handlers/signup.ts b/lib/bigcommerce/api/customers/handlers/signup.ts index 911919088..ff45adadb 100644 --- a/lib/bigcommerce/api/customers/handlers/signup.ts +++ b/lib/bigcommerce/api/customers/handlers/signup.ts @@ -18,51 +18,45 @@ const signup: SignupHandlers['signup'] = async ({ // Passwords must be at least 7 characters and contain both alphabetic // and numeric characters. - let result: { data?: any } = {} + try { + await config.storeApiFetch('/v3/customers', { + method: 'POST', + body: JSON.stringify([ + { + first_name: firstName, + last_name: lastName, + email, + authentication: { + new_password: password, + }, + }, + ]), + }) + } catch (error) { + if (error instanceof BigcommerceApiError && error.status === 422) { + const hasEmailError = '0.email' in error.data?.errors - // try { - // result = await config.storeApiFetch('/v3/customers', { - // method: 'POST', - // body: JSON.stringify([ - // { - // first_name: firstName, - // last_name: lastName, - // email, - // authentication: { - // new_password: password, - // }, - // }, - // ]), - // }) - // } catch (error) { - // if (error instanceof BigcommerceApiError && error.status === 422) { - // const hasEmailError = '0.email' in error.data?.errors + // If there's an error with the email, it most likely means it's duplicated + if (hasEmailError) { + return res.status(400).json({ + data: null, + errors: [ + { + message: 'The email is already in use', + code: 'duplicated_email', + }, + ], + }) + } + } - // // If there's an error with the email, it most likely means it's duplicated - // if (hasEmailError) { - // return res.status(400).json({ - // data: null, - // errors: [ - // { - // message: 'The email is already in use', - // code: 'duplicated_email', - // }, - // ], - // }) - // } - // } + throw error + } - // throw error - // } + // Login the customer right after creating it + await login({ variables: { email, password }, res, config }) - console.log('DATA', result.data) - - // TODO: Currently not working, fix this asap. - const loginData = await login({ variables: { email, password }, config }) - - console.log('LOGIN DATA', loginData) - - res.status(200).json({ data: result.data ?? null }) + res.status(200).json({ data: null }) } export default signup diff --git a/lib/bigcommerce/api/operations/get-all-product-paths.ts b/lib/bigcommerce/api/operations/get-all-product-paths.ts index 278fb78fe..483160f6b 100644 --- a/lib/bigcommerce/api/operations/get-all-product-paths.ts +++ b/lib/bigcommerce/api/operations/get-all-product-paths.ts @@ -49,9 +49,9 @@ async function getAllProductPaths({ config = getConfig(config) // RecursivePartial forces the method to check for every prop in the data, which is // required in case there's a custom `query` - const data = await config.fetch>( - query - ) + const { data } = await config.fetch< + RecursivePartial + >(query) const products = data.site?.products?.edges return { diff --git a/lib/bigcommerce/api/operations/get-all-products.ts b/lib/bigcommerce/api/operations/get-all-products.ts index c4408a4b6..699fa8310 100644 --- a/lib/bigcommerce/api/operations/get-all-products.ts +++ b/lib/bigcommerce/api/operations/get-all-products.ts @@ -111,7 +111,7 @@ async function getAllProducts({ // RecursivePartial forces the method to check for every prop in the data, which is // required in case there's a custom `query` - const data = await config.fetch>( + const { data } = await config.fetch>( query, { variables } ) diff --git a/lib/bigcommerce/api/operations/get-product.ts b/lib/bigcommerce/api/operations/get-product.ts index a4f91afdb..95b3bdc31 100644 --- a/lib/bigcommerce/api/operations/get-product.ts +++ b/lib/bigcommerce/api/operations/get-product.ts @@ -71,9 +71,10 @@ async function getProduct({ ...vars, path: slug ? `/${slug}/` : vars.path!, } - const data = await config.fetch>(query, { - variables, - }) + const { data } = await config.fetch>( + query, + { variables } + ) const product = data.site?.route?.node if (product?.__typename === 'Product') { diff --git a/lib/bigcommerce/api/operations/get-site-info.ts b/lib/bigcommerce/api/operations/get-site-info.ts index a992ba980..cc9063b07 100644 --- a/lib/bigcommerce/api/operations/get-site-info.ts +++ b/lib/bigcommerce/api/operations/get-site-info.ts @@ -90,9 +90,10 @@ async function getSiteInfo({ config = getConfig(config) // RecursivePartial forces the method to check for every prop in the data, which is // required in case there's a custom `query` - const data = await config.fetch>(query, { - variables, - }) + const { data } = await config.fetch>( + query, + { variables } + ) const categories = data.site?.categoryTree const brands = data.site?.brands?.edges diff --git a/lib/bigcommerce/api/operations/login.ts b/lib/bigcommerce/api/operations/login.ts index a73e6beb4..c371e71d5 100644 --- a/lib/bigcommerce/api/operations/login.ts +++ b/lib/bigcommerce/api/operations/login.ts @@ -1,8 +1,10 @@ +import type { ServerResponse } from 'http' import type { LoginMutation, LoginMutationVariables, } from 'lib/bigcommerce/schema' import type { RecursivePartial } from '../utils/types' +import concatHeader from '../utils/concat-cookie' import { BigcommerceConfig, getConfig } from '..' export const loginMutation = /* GraphQL */ ` @@ -20,28 +22,41 @@ export type LoginVariables = LoginMutationVariables async function login(opts: { variables: LoginVariables config?: BigcommerceConfig + res: ServerResponse }): Promise async function login(opts: { query: string variables: V + res: ServerResponse config?: BigcommerceConfig }): Promise> async function login({ query = loginMutation, variables, + res: response, config, }: { query?: string variables: LoginVariables + res: ServerResponse config?: BigcommerceConfig }): Promise { config = getConfig(config) - const data = await config.fetch>(query, { - variables, - }) + const { data, res } = await config.fetch>( + query, + { variables } + ) + const cookie = res.headers.get('Set-Cookie') + + if (cookie && typeof cookie === 'string') { + response.setHeader( + 'Set-Cookie', + concatHeader(response.getHeader('Set-Cookie'), cookie)! + ) + } return { result: data.login?.result, diff --git a/lib/bigcommerce/api/utils/concat-cookie.ts b/lib/bigcommerce/api/utils/concat-cookie.ts new file mode 100644 index 000000000..362e12e99 --- /dev/null +++ b/lib/bigcommerce/api/utils/concat-cookie.ts @@ -0,0 +1,14 @@ +type Header = string | number | string[] | undefined + +export default function concatHeader(prev: Header, val: Header) { + if (!val) return prev + if (!prev) return val + + if (Array.isArray(prev)) return prev.concat(String(val)) + + prev = String(prev) + + if (Array.isArray(val)) return [prev].concat(val) + + return [prev, String(val)] +} diff --git a/lib/bigcommerce/api/utils/fetch-graphql-api.ts b/lib/bigcommerce/api/utils/fetch-graphql-api.ts index 1ab323832..4adec7f4d 100644 --- a/lib/bigcommerce/api/utils/fetch-graphql-api.ts +++ b/lib/bigcommerce/api/utils/fetch-graphql-api.ts @@ -1,18 +1,21 @@ -import { CommerceAPIFetchOptions } from 'lib/commerce/api' +import type { GraphQLFetcher } from 'lib/commerce/api' import { getConfig } from '..' import log from '@lib/logger' -export default async function fetchGraphqlApi( +const fetchGraphqlApi: GraphQLFetcher = async ( query: string, - { variables, preview }: CommerceAPIFetchOptions = {} -): Promise { + { variables, preview } = {}, + fetchOptions +) => { // log.warn(query) const config = getConfig() const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), { + ...fetchOptions, method: 'POST', headers: { - 'Content-Type': 'application/json', Authorization: `Bearer ${config.apiToken}`, + ...fetchOptions?.headers, + 'Content-Type': 'application/json', }, body: JSON.stringify({ query, @@ -20,22 +23,13 @@ export default async function fetchGraphqlApi( }), }) - // console.log('HEADERS', getRawHeaders(res)) - const json = await res.json() if (json.errors) { console.error(json.errors) throw new Error('Failed to fetch BigCommerce API') } - return json.data + + return { data: json.data, res } } -function getRawHeaders(res: Response) { - const headers: { [key: string]: string } = {} - - res.headers.forEach((value, key) => { - headers[key] = value - }) - - return headers -} +export default fetchGraphqlApi diff --git a/lib/commerce/api/index.ts b/lib/commerce/api/index.ts index 38ed70d4a..db55e1daa 100644 --- a/lib/commerce/api/index.ts +++ b/lib/commerce/api/index.ts @@ -3,14 +3,29 @@ export interface CommerceAPIConfig { apiToken: string cartCookie: string cartCookieMaxAge: number - fetch( + fetch( query: string, - queryData?: CommerceAPIFetchOptions - ): Promise + queryData?: CommerceAPIFetchOptions, + fetchOptions?: RequestInit + ): Promise> } -export interface CommerceAPIFetchOptions { - variables?: V +export type GraphQLFetcher< + Data extends GraphQLFetcherResult = GraphQLFetcherResult, + Variables = any +> = ( + query: string, + queryData?: CommerceAPIFetchOptions, + fetchOptions?: RequestInit +) => Promise + +export interface GraphQLFetcherResult { + data: Data + res: Response +} + +export interface CommerceAPIFetchOptions { + variables?: Variables preview?: boolean }