mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 20:26:49 +00:00
Added framework folder
This commit is contained in:
14
framework/bigcommerce/api/utils/concat-cookie.ts
Normal file
14
framework/bigcommerce/api/utils/concat-cookie.ts
Normal file
@@ -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)]
|
||||
}
|
58
framework/bigcommerce/api/utils/create-api-handler.ts
Normal file
58
framework/bigcommerce/api/utils/create-api-handler.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
|
||||
import { BigcommerceConfig, getConfig } from '..'
|
||||
|
||||
export type BigcommerceApiHandler<
|
||||
T = any,
|
||||
H extends BigcommerceHandlers = {},
|
||||
Options extends {} = {}
|
||||
> = (
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse<BigcommerceApiResponse<T>>,
|
||||
config: BigcommerceConfig,
|
||||
handlers: H,
|
||||
// Custom configs that may be used by a particular handler
|
||||
options: Options
|
||||
) => void | Promise<void>
|
||||
|
||||
export type BigcommerceHandler<T = any, Body = null> = (options: {
|
||||
req: NextApiRequest
|
||||
res: NextApiResponse<BigcommerceApiResponse<T>>
|
||||
config: BigcommerceConfig
|
||||
body: Body
|
||||
}) => void | Promise<void>
|
||||
|
||||
export type BigcommerceHandlers<T = any> = {
|
||||
[k: string]: BigcommerceHandler<T, any>
|
||||
}
|
||||
|
||||
export type BigcommerceApiResponse<T> = {
|
||||
data: T | null
|
||||
errors?: { message: string; code?: string }[]
|
||||
}
|
||||
|
||||
export default function createApiHandler<
|
||||
T = any,
|
||||
H extends BigcommerceHandlers = {},
|
||||
Options extends {} = {}
|
||||
>(
|
||||
handler: BigcommerceApiHandler<T, H, Options>,
|
||||
handlers: H,
|
||||
defaultOptions: Options
|
||||
) {
|
||||
return function getApiHandler({
|
||||
config,
|
||||
operations,
|
||||
options,
|
||||
}: {
|
||||
config?: BigcommerceConfig
|
||||
operations?: Partial<H>
|
||||
options?: Options extends {} ? Partial<Options> : never
|
||||
} = {}): NextApiHandler {
|
||||
const ops = { ...operations, ...handlers }
|
||||
const opts = { ...defaultOptions, ...options }
|
||||
|
||||
return function apiHandler(req, res) {
|
||||
return handler(req, res, getConfig(config), ops, opts)
|
||||
}
|
||||
}
|
||||
}
|
25
framework/bigcommerce/api/utils/errors.ts
Normal file
25
framework/bigcommerce/api/utils/errors.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { Response } from '@vercel/fetch'
|
||||
|
||||
// Used for GraphQL errors
|
||||
export class BigcommerceGraphQLError extends Error {}
|
||||
|
||||
export class BigcommerceApiError extends Error {
|
||||
status: number
|
||||
res: Response
|
||||
data: any
|
||||
|
||||
constructor(msg: string, res: Response, data?: any) {
|
||||
super(msg)
|
||||
this.name = 'BigcommerceApiError'
|
||||
this.status = res.status
|
||||
this.res = res
|
||||
this.data = data
|
||||
}
|
||||
}
|
||||
|
||||
export class BigcommerceNetworkError extends Error {
|
||||
constructor(msg: string) {
|
||||
super(msg)
|
||||
this.name = 'BigcommerceNetworkError'
|
||||
}
|
||||
}
|
38
framework/bigcommerce/api/utils/fetch-graphql-api.ts
Normal file
38
framework/bigcommerce/api/utils/fetch-graphql-api.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { FetcherError } from '@commerce/utils/errors'
|
||||
import type { GraphQLFetcher } from '@commerce/api'
|
||||
import { getConfig } from '..'
|
||||
import fetch from './fetch'
|
||||
|
||||
const fetchGraphqlApi: GraphQLFetcher = 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
|
71
framework/bigcommerce/api/utils/fetch-store-api.ts
Normal file
71
framework/bigcommerce/api/utils/fetch-store-api.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import type { RequestInit, Response } from '@vercel/fetch'
|
||||
import { getConfig } from '..'
|
||||
import { BigcommerceApiError, BigcommerceNetworkError } from './errors'
|
||||
import fetch from './fetch'
|
||||
|
||||
export default async function fetchStoreApi<T>(
|
||||
endpoint: string,
|
||||
options?: RequestInit
|
||||
): Promise<T> {
|
||||
const config = getConfig()
|
||||
let res: Response
|
||||
|
||||
try {
|
||||
res = await fetch(config.storeApiUrl + endpoint, {
|
||||
...options,
|
||||
headers: {
|
||||
...options?.headers,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': config.storeApiToken,
|
||||
'X-Auth-Client': config.storeApiClientId,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
throw new BigcommerceNetworkError(
|
||||
`Fetch to Bigcommerce failed: ${error.message}`
|
||||
)
|
||||
}
|
||||
|
||||
const contentType = res.headers.get('Content-Type')
|
||||
const isJSON = contentType?.includes('application/json')
|
||||
|
||||
if (!res.ok) {
|
||||
const data = isJSON ? await res.json() : await getTextOrNull(res)
|
||||
const headers = getRawHeaders(res)
|
||||
const msg = `Big Commerce API error (${
|
||||
res.status
|
||||
}) \nHeaders: ${JSON.stringify(headers, null, 2)}\n${
|
||||
typeof data === 'string' ? data : JSON.stringify(data, null, 2)
|
||||
}`
|
||||
|
||||
throw new BigcommerceApiError(msg, res, data)
|
||||
}
|
||||
|
||||
if (res.status !== 204 && !isJSON) {
|
||||
throw new BigcommerceApiError(
|
||||
`Fetch to Bigcommerce API failed, expected JSON content but found: ${contentType}`,
|
||||
res
|
||||
)
|
||||
}
|
||||
|
||||
// If something was removed, the response will be empty
|
||||
return res.status === 204 ? null : await res.json()
|
||||
}
|
||||
|
||||
function getRawHeaders(res: Response) {
|
||||
const headers: { [key: string]: string } = {}
|
||||
|
||||
res.headers.forEach((value, key) => {
|
||||
headers[key] = value
|
||||
})
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
function getTextOrNull(res: Response) {
|
||||
try {
|
||||
return res.text()
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}
|
3
framework/bigcommerce/api/utils/fetch.ts
Normal file
3
framework/bigcommerce/api/utils/fetch.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import zeitFetch from '@vercel/fetch'
|
||||
|
||||
export default zeitFetch()
|
5
framework/bigcommerce/api/utils/filter-edges.ts
Normal file
5
framework/bigcommerce/api/utils/filter-edges.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default function filterEdges<T>(
|
||||
edges: (T | null | undefined)[] | null | undefined
|
||||
) {
|
||||
return edges?.filter((edge): edge is T => !!edge) ?? []
|
||||
}
|
20
framework/bigcommerce/api/utils/get-cart-cookie.ts
Normal file
20
framework/bigcommerce/api/utils/get-cart-cookie.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { serialize, CookieSerializeOptions } from 'cookie'
|
||||
|
||||
export default function getCartCookie(
|
||||
name: string,
|
||||
cartId?: string,
|
||||
maxAge?: number
|
||||
) {
|
||||
const options: CookieSerializeOptions =
|
||||
cartId && maxAge
|
||||
? {
|
||||
maxAge,
|
||||
expires: new Date(Date.now() + maxAge * 1000),
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
}
|
||||
: { maxAge: -1, path: '/' } // Removes the cookie
|
||||
|
||||
return serialize(name, cartId || '', options)
|
||||
}
|
28
framework/bigcommerce/api/utils/is-allowed-method.ts
Normal file
28
framework/bigcommerce/api/utils/is-allowed-method.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
|
||||
export default function isAllowedMethod(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
allowedMethods: string[]
|
||||
) {
|
||||
const methods = allowedMethods.includes('OPTIONS')
|
||||
? allowedMethods
|
||||
: [...allowedMethods, 'OPTIONS']
|
||||
|
||||
if (!req.method || !methods.includes(req.method)) {
|
||||
res.status(405)
|
||||
res.setHeader('Allow', methods.join(', '))
|
||||
res.end()
|
||||
return false
|
||||
}
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(200)
|
||||
res.setHeader('Allow', methods.join(', '))
|
||||
res.setHeader('Content-Length', '0')
|
||||
res.end()
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
14
framework/bigcommerce/api/utils/parse-item.ts
Normal file
14
framework/bigcommerce/api/utils/parse-item.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { ItemBody as WishlistItemBody } from '../wishlist'
|
||||
import type { ItemBody } from '../cart'
|
||||
|
||||
export const parseWishlistItem = (item: WishlistItemBody) => ({
|
||||
product_id: item.productId,
|
||||
variant_id: item.variantId,
|
||||
})
|
||||
|
||||
export const parseCartItem = (item: ItemBody) => ({
|
||||
quantity: item.quantity,
|
||||
product_id: item.productId,
|
||||
variant_id: item.variantId,
|
||||
option_selections: item.optionSelections
|
||||
})
|
21
framework/bigcommerce/api/utils/set-product-locale-meta.ts
Normal file
21
framework/bigcommerce/api/utils/set-product-locale-meta.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { ProductNode } from '../operations/get-all-products'
|
||||
import type { RecursivePartial } from './types'
|
||||
|
||||
export default function setProductLocaleMeta(
|
||||
node: RecursivePartial<ProductNode>
|
||||
) {
|
||||
if (node.localeMeta?.edges) {
|
||||
node.localeMeta.edges = node.localeMeta.edges.filter((edge) => {
|
||||
const { key, value } = edge?.node ?? {}
|
||||
if (key && key in node) {
|
||||
;(node as any)[key] = value
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if (!node.localeMeta.edges.length) {
|
||||
delete node.localeMeta
|
||||
}
|
||||
}
|
||||
}
|
7
framework/bigcommerce/api/utils/types.ts
Normal file
7
framework/bigcommerce/api/utils/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export type RecursivePartial<T> = {
|
||||
[P in keyof T]?: RecursivePartial<T[P]>
|
||||
}
|
||||
|
||||
export type RecursiveRequired<T> = {
|
||||
[P in keyof T]-?: RecursiveRequired<T[P]>
|
||||
}
|
Reference in New Issue
Block a user