From 1c3714bf517db9aaebef747bb4a4f3b2c0c0fbee Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Sun, 11 Oct 2020 00:19:11 -0500 Subject: [PATCH] Allow API operations to be more customizable --- lib/bigcommerce/api/cart/handlers/get-cart.ts | 29 +++++++++++ .../api/{cart.ts => cart/index.ts} | 44 +++++++++-------- lib/bigcommerce/api/customers.ts | 48 +++++++++++++++++++ .../api/utils/create-api-handler.ts | 34 +++++++++++-- lib/bigcommerce/api/utils/get-cart-cookie.ts | 20 ++++++++ 5 files changed, 149 insertions(+), 26 deletions(-) create mode 100644 lib/bigcommerce/api/cart/handlers/get-cart.ts rename lib/bigcommerce/api/{cart.ts => cart/index.ts} (85%) create mode 100644 lib/bigcommerce/api/customers.ts create mode 100644 lib/bigcommerce/api/utils/get-cart-cookie.ts diff --git a/lib/bigcommerce/api/cart/handlers/get-cart.ts b/lib/bigcommerce/api/cart/handlers/get-cart.ts new file mode 100644 index 000000000..5b6e645c2 --- /dev/null +++ b/lib/bigcommerce/api/cart/handlers/get-cart.ts @@ -0,0 +1,29 @@ +import { BigcommerceApiError } from '../../utils/errors' +import getCartCookie from '../../utils/get-cart-cookie' +import type { Cart, CartHandlers } from '..' + +// Return current cart info +const getCart: CartHandlers['getCart'] = async ({ + res, + body: { cartId }, + config, +}) => { + let result: { data?: Cart } = {} + + try { + result = await config.storeApiFetch( + `/v3/carts/${cartId}?include=redirect_urls` + ) + } 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 ?? null }) +} + +export default getCart diff --git a/lib/bigcommerce/api/cart.ts b/lib/bigcommerce/api/cart/index.ts similarity index 85% rename from lib/bigcommerce/api/cart.ts rename to lib/bigcommerce/api/cart/index.ts index b7fd155e0..23e92dbd6 100644 --- a/lib/bigcommerce/api/cart.ts +++ b/lib/bigcommerce/api/cart/index.ts @@ -1,9 +1,11 @@ import { serialize, CookieSerializeOptions } from 'cookie' -import isAllowedMethod from './utils/is-allowed-method' +import isAllowedMethod from '../utils/is-allowed-method' import createApiHandler, { BigcommerceApiHandler, -} from './utils/create-api-handler' -import { BigcommerceApiError } from './utils/errors' + BigcommerceHandler, +} from '../utils/create-api-handler' +import { BigcommerceApiError } from '../utils/errors' +import getCart from './handlers/get-cart' type Body = Partial | undefined @@ -40,10 +42,19 @@ export type Cart = { // TODO: add missing fields } +export type CartHandlers = { + getCart: BigcommerceHandler +} + const METHODS = ['GET', 'POST', 'PUT', 'DELETE'] // TODO: a complete implementation should have schema validation for `req.body` -const cartApi: BigcommerceApiHandler = async (req, res, config) => { +const cartApi: BigcommerceApiHandler = async ( + req, + res, + config, + handlers +) => { if (!isAllowedMethod(req, res, METHODS)) return const { cookies } = req @@ -52,22 +63,7 @@ const cartApi: BigcommerceApiHandler = async (req, res, config) => { try { // Return current cart info if (req.method === 'GET') { - let result: { data?: Cart } = {} - - try { - result = await config.storeApiFetch( - `/v3/carts/${cartId}?include=redirect_urls` - ) - } 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 - } - } - - return res.status(200).json({ data: result.data ?? null }) + return await handlers['getCart']({ req, res, config, body: { cartId } }) } // Create or add an item to the cart @@ -192,4 +188,10 @@ const parseItem = (item: ItemBody) => ({ variant_id: item.variantId, }) -export default createApiHandler(cartApi) +const handlers = { + getCart, +} + +const h = createApiHandler(cartApi, handlers) + +export default h diff --git a/lib/bigcommerce/api/customers.ts b/lib/bigcommerce/api/customers.ts new file mode 100644 index 000000000..0ac75e3b6 --- /dev/null +++ b/lib/bigcommerce/api/customers.ts @@ -0,0 +1,48 @@ +import createApiHandler, { + BigcommerceApiHandler, + BigcommerceHandler, +} from './utils/create-api-handler' +import isAllowedMethod from './utils/is-allowed-method' +import { BigcommerceApiError } from './utils/errors' + +export type Customer = any + +const METHODS = ['POST'] + +const customersApi: BigcommerceApiHandler = async ( + req, + res, + config +) => { + if (!isAllowedMethod(req, res, METHODS)) return + + try { + if (req.method === 'POST') { + // let result = {} as any + // const + // result = await config.storeApiFetch('/v3/customers') + } + } 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 }] }) + } +} + +const createCustomer: BigcommerceHandler = ({ + req, + res, + body, + config, +}) => {} + +const handlers = { + createCustomer, +} + +export default createApiHandler(customersApi, handlers) diff --git a/lib/bigcommerce/api/utils/create-api-handler.ts b/lib/bigcommerce/api/utils/create-api-handler.ts index 68bf92d38..0e4a1b959 100644 --- a/lib/bigcommerce/api/utils/create-api-handler.ts +++ b/lib/bigcommerce/api/utils/create-api-handler.ts @@ -1,23 +1,47 @@ import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next' import { BigcommerceConfig, getConfig } from '..' -export type BigcommerceApiHandler = ( +export type BigcommerceApiHandler< + T = any, + H extends BigcommerceHandlers = {} +> = ( req: NextApiRequest, res: NextApiResponse>, - config: BigcommerceConfig + config: BigcommerceConfig, + handlers: H ) => void | Promise +export type BigcommerceHandler = (options: { + req: NextApiRequest + res: NextApiResponse> + config: BigcommerceConfig + body: Body +}) => void | Promise + +export type BigcommerceHandlers = { + [k: string]: BigcommerceHandler +} + export type BigcommerceApiResponse = { data: T | null errors?: { message: string }[] } -export default function createApiHandler(handler: BigcommerceApiHandler) { +export default function createApiHandler( + handler: BigcommerceApiHandler, + handlers: H +) { return function getApiHandler({ config, - }: { config?: BigcommerceConfig } = {}): NextApiHandler { + operations, + }: { + config?: BigcommerceConfig + operations?: Partial + } = {}): NextApiHandler { + const ops = { ...operations, ...handlers } + return function apiHandler(req, res) { - return handler(req, res, getConfig(config)) + return handler(req, res, getConfig(config), ops) } } } diff --git a/lib/bigcommerce/api/utils/get-cart-cookie.ts b/lib/bigcommerce/api/utils/get-cart-cookie.ts new file mode 100644 index 000000000..7ca6cd5e4 --- /dev/null +++ b/lib/bigcommerce/api/utils/get-cart-cookie.ts @@ -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) +}