diff --git a/framework/bigcommerce/api/cart/add-item.ts b/framework/bigcommerce/api/cart/add-item.ts index 8c83c4f7b..77c8a19ba 100644 --- a/framework/bigcommerce/api/cart/add-item.ts +++ b/framework/bigcommerce/api/cart/add-item.ts @@ -1,7 +1,7 @@ +import { normalizeCart } from '@framework/lib/normalize' import { parseCartItem } from '../utils/parse-item' import getCartCookie from '../utils/get-cart-cookie' import type { CartHandlers } from '.' -import { normalizeCart } from '@framework/lib/normalize' const addItem: CartHandlers['addItem'] = async ({ res, diff --git a/framework/bigcommerce/api/cart/get-cart.ts b/framework/bigcommerce/api/cart/get-cart.ts new file mode 100644 index 000000000..52a33b630 --- /dev/null +++ b/framework/bigcommerce/api/cart/get-cart.ts @@ -0,0 +1,35 @@ +import { normalizeCart } from '@framework/lib/normalize' +import { BigcommerceApiError } from '../utils/errors' +import getCartCookie from '../utils/get-cart-cookie' +import type { BigcommerceCart } from '../../types' +import type { CartHandlers } from '.' + +// Return current cart info +const getCart: CartHandlers['getCart'] = async ({ + res, + body: { cartId }, + config, +}) => { + let result: { data?: BigcommerceCart } = {} + + if (cartId) { + try { + result = await config.storeApiFetch( + `/v3/carts/${cartId}?include=line_items.physical_items.options` + ) + } catch (error) { + if (error instanceof BigcommerceApiError && error.status === 404) { + // Remove the cookie if it exists but the cart wasn't found + res.setHeader('Set-Cookie', getCartCookie(config.cartCookie)) + } else { + throw error + } + } + } + + res.status(200).json({ + data: result.data ? normalizeCart(result.data) : null, + }) +} + +export default getCart diff --git a/framework/bigcommerce/api/cart/index.ts b/framework/bigcommerce/api/cart/index.ts index 22adb57d4..5d825d952 100644 --- a/framework/bigcommerce/api/cart/index.ts +++ b/framework/bigcommerce/api/cart/index.ts @@ -1,86 +1,34 @@ -import { CartHandlers as CartHandlersCore } from '@commerce/api' -import isAllowedMethod from '../utils/is-allowed-method' -import createApiHandler, { - BigcommerceApiHandler, -} from '../utils/create-api-handler' -import { BigcommerceApiError } from '../utils/errors' -import getCart from './handlers/get-cart' -import addItem from './handlers/add-item' +import { EndpointSchema } from '@commerce/api' +import getCart from './get-cart' +import addItem from './add-item' import updateItem from './handlers/update-item' import removeItem from './handlers/remove-item' import type { - BigcommerceCart, GetCartHandlerBody, AddCartItemHandlerBody, UpdateCartItemHandlerBody, RemoveCartItemHandlerBody, Cart, } from '../../types' -import { APIHandler } from '@commerce/api/utils/types' -import { CommerceAPI } from '..' +import type { CommerceAPIEndpoints } from '..' -export type CartHandlers< - C extends CommerceAPI = CommerceAPI -> = CartHandlersCore< - C, +export type CartEndpointSchema = EndpointSchema< + 'cart', { - getCart: APIHandler, Cart | null, GetCartHandlerBody> - addItem: APIHandler, Cart, AddCartItemHandlerBody> - updateItem: APIHandler, Cart, UpdateCartItemHandlerBody> - removeItem: APIHandler, Cart, RemoveCartItemHandlerBody> + options: {} + operations: { + getCart: { + data: Cart | null + body: GetCartHandlerBody + options: { yay: string } + } + addItem: { data: Cart; body: AddCartItemHandlerBody; options: {} } + updateItem: { data: Cart; body: UpdateCartItemHandlerBody; options: {} } + removeItem: { data: Cart; body: RemoveCartItemHandlerBody; options: {} } + } } > -const METHODS = ['GET', 'POST', 'PUT', 'DELETE'] +export type CartAPI = CommerceAPIEndpoints['cart'] -// TODO: a complete implementation should have schema validation for `req.body` -const cartApi: BigcommerceApiHandler = async ( - req, - res, - config, - handlers -) => { - if (!isAllowedMethod(req, res, METHODS)) return - - const { cookies } = req - const cartId = cookies[config.cartCookie] - - try { - // Return current cart info - if (req.method === 'GET') { - const body = { cartId } - return await handlers['getCart']({ req, res, config, body }) - } - - // Create or add an item to the cart - if (req.method === 'POST') { - const body = { ...req.body, cartId } - return await handlers['addItem']({ req, res, config, body }) - } - - // Update item in cart - if (req.method === 'PUT') { - const body = { ...req.body, cartId } - return await handlers['updateItem']({ req, res, config, body }) - } - - // Remove an item from the cart - if (req.method === 'DELETE') { - const body = { ...req.body, cartId } - return await handlers['removeItem']({ req, res, config, body }) - } - } catch (error) { - console.error(error) - - const message = - error instanceof BigcommerceApiError - ? 'An unexpected error ocurred with the Bigcommerce API' - : 'An unexpected error ocurred' - - res.status(500).json({ data: null, errors: [{ message }] }) - } -} - -export const handlers = { getCart, addItem, updateItem, removeItem } - -export default createApiHandler(cartApi, handlers, {}) +export const operations = { getCart, addItem } diff --git a/framework/bigcommerce/api/index.ts b/framework/bigcommerce/api/index.ts index d11f168b8..e9acbfffc 100644 --- a/framework/bigcommerce/api/index.ts +++ b/framework/bigcommerce/api/index.ts @@ -2,9 +2,11 @@ import type { RequestInit } from '@vercel/fetch' import { CommerceAPI as CoreCommerceAPI, CommerceAPIConfig, + GetEndpointsSchema, } from '@commerce/api' import fetchGraphqlApi from './utils/fetch-graphql-api' import fetchStoreApi from './utils/fetch-store-api' +import { CartEndpointSchema } from './cart' export interface BigcommerceConfig extends CommerceAPIConfig { // Indicates if the returned metadata with translations should be applied to the @@ -104,14 +106,19 @@ export const provider = { export type Provider = typeof provider +export type EndpointsSchema = { cart: CartEndpointSchema } + export class CommerceAPI< - P extends Provider = Provider -> extends CoreCommerceAPI

{ + P extends Provider = Provider, + E extends EndpointsSchema = EndpointsSchema +> extends CoreCommerceAPI { constructor(readonly provider: P = provider) { super(provider) } } +export type CommerceAPIEndpoints = GetEndpointsSchema + export function getConfig(userConfig?: Partial) { return config.getConfig(userConfig) } diff --git a/framework/bigcommerce/types.ts b/framework/bigcommerce/types.ts index beeab0223..d0d711f3d 100644 --- a/framework/bigcommerce/types.ts +++ b/framework/bigcommerce/types.ts @@ -25,6 +25,7 @@ export type BigcommerceCart = { export type Cart = Core.Cart & { lineItems: LineItem[] + core: string } export type LineItem = Core.LineItem diff --git a/framework/commerce/api/index.ts b/framework/commerce/api/index.ts index e2bc4f225..a1a3e4191 100644 --- a/framework/commerce/api/index.ts +++ b/framework/commerce/api/index.ts @@ -5,8 +5,14 @@ import type { Cart } from '../types' export type CartEndpoint = APIEndpoint, any> -export type CartHandlersBase = { - getCart: APIHandler, Cart | null, any> +export type CartHandlersBase = { + getCart: APIHandler< + any, + CartHandlersBase, + Cart | null, + any, + { yay: string } + > addItem: APIHandler, Cart, any> updateItem: APIHandler, Cart, any> removeItem: APIHandler, Cart, any> @@ -17,13 +23,103 @@ export type CartHandlers< T extends CartHandlersBase = CartHandlersBase > = T -export type Endpoints = CartEndpoint +export type CartHandlersType = { + getCart: { data: Cart | null; body: any; options: {} } + addItem: { data: Cart; body: any; options: {} } + updateItem: { data: Cart; body: any; options: {} } + removeItem: { data: Cart; body: any; options: {} } +} -export type EndpointHandlers = E extends APIEndpoint +export type CartHandlers2< + C extends CommerceAPI, + T extends CartHandlersType = CartHandlersType +> = { + getCart: APIHandler< + any, + CartHandlersBase, + Cart | null, + any, + { yay: string } + > + addItem: APIHandler, Cart, any> + updateItem: APIHandler, Cart, any> + removeItem: APIHandler, Cart, any> +} + +export type EndpointsSchema = { + cart?: { + options: {} + operations: { + getCart: { data?: Cart | null; body?: any } + addItem: { data?: Cart; body?: any } + updateItem: { data?: Cart; body?: any } + removeItem: { data?: Cart; body?: any } + } + } +} + +export type GetEndpointsSchema< + C extends CommerceAPI, + Schema extends EndpointsSchema = C extends CommerceAPI + ? E + : never +> = { + [E in keyof EndpointsSchema]-?: Schema[E] & { + endpoint: Endpoint> + handlers: EndpointHandlers> + } +} + +type X = Endpoint> + +export type EndpointSchemaBase = { + options: {} + operations: { + [k: string]: { data?: any; body?: any } + } +} + +export type OperationData = T extends { data?: infer D; body?: any } + ? D + : never + +export type EndpointSchema< + E extends keyof EndpointsSchema, + Handlers extends EndpointsSchema[E] +> = Handlers + +export type Endpoint< + C extends CommerceAPI, + E extends EndpointSchemaBase +> = APIEndpoint< + C, + EndpointHandlers, + OperationData, + E['options'] +> + +export type EndpointHandlers< + C extends CommerceAPI, + E extends EndpointSchemaBase +> = { + [H in keyof E['operations']]: APIHandler< + C, + EndpointHandlers, + E['operations'][H]['data'], + E['operations'][H]['body'], + E['options'] + > +} + +export type CommerceEndpointsSchema = C extends CommerceAPI + ? E + : never + +export type HandlerOperations = E extends APIEndpoint ? T : never -export type EndpointOptions = E extends APIEndpoint +export type HandlerOptions = E extends APIEndpoint ? T : never @@ -33,7 +129,7 @@ export type APIProvider = { export class CommerceAPI< P extends APIProvider = APIProvider, - E extends Endpoints = Endpoints + E extends EndpointsSchema = EndpointsSchema > { constructor(readonly provider: P) { this.provider = provider @@ -53,8 +149,8 @@ export class CommerceAPI< endpoint(context: { handler: E config?: P['config'] - operations: EndpointHandlers - options?: EndpointOptions + operations: HandlerOperations + options?: HandlerOptions }): NextApiHandler { const commerce = this const cfg = this.getConfig(context.config) diff --git a/framework/commerce/api/utils/types.ts b/framework/commerce/api/utils/types.ts index c971d64f8..27a95df40 100644 --- a/framework/commerce/api/utils/types.ts +++ b/framework/commerce/api/utils/types.ts @@ -37,8 +37,8 @@ export type APIHandler< context: APIHandlerContext & { body: Body } ) => void | Promise -export type APIHandlers = { - [k: string]: APIHandler +export type APIHandlers = { + [k: string]: APIHandler } export type APIEndpoint< diff --git a/pages/api/bigcommerce/cart.ts b/pages/api/bigcommerce/cart.ts index ff2e1c981..ee9e1e04c 100644 --- a/pages/api/bigcommerce/cart.ts +++ b/pages/api/bigcommerce/cart.ts @@ -1,7 +1,5 @@ -import cartApi from '@framework/api/cart' import cart from '@commerce/api/endpoints/cart' +import { operations } from '@framework/api/cart' import commerce from '@lib/api/commerce' -const x = commerce.endpoint({ handler: cart, operations: {} as any }) - -export default cartApi() +export default commerce.endpoint({ handler: cart, operations })