Update api handlers

This commit is contained in:
Catalin Pinte 2022-10-04 10:12:58 +03:00
parent 30a94cf857
commit 3a3883ada0
30 changed files with 251 additions and 99 deletions

View File

@ -17,7 +17,7 @@ const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
res.redirect('/cart')
return
}
const { data } = await config.storeApiFetch(
const { data } = await config.storeApiFetch<any>(
`/v3/carts/${cartId}/redirect_urls`,
{
method: 'POST',
@ -42,7 +42,7 @@ const getCheckout: CheckoutEndpoint['handlers']['getCheckout'] = async ({
store_hash: config.storeHash,
customer_id: customerId,
channel_id: config.storeChannelId,
redirect_to: data.checkout_url.replace(config.storeUrl, ""),
redirect_to: data.checkout_url.replace(config.storeUrl, ''),
}
let token = jwt.sign(payload, config.storeApiClientSecret!, {
algorithm: 'HS256',

View File

@ -17,7 +17,6 @@ const productsEndpoint: GetAPISchema<
categoryId: req.query.categoryId,
brandId: req.query.brandId,
sort: req.query.sort,
locale: req.query.locale,
})
return handlers['getProducts']({ ...ctx, body })

View File

@ -17,7 +17,7 @@ const checkoutEndpoint: GetAPISchema<
const { cookies } = req
const cartId = cookies[config.cartCookie]
// Create checkout
// Get checkout
if (req.method === 'GET') {
const body = { ...req.body, cartId }
return handlers['getCheckout']({ ...ctx, body })

View File

@ -2,6 +2,12 @@ import type { CustomerAddressSchema } from '../../../types/customer/address'
import type { GetAPISchema } from '../..'
import validateHandlers from '../../utils/validate-handlers'
import {
addAddressBodySchema,
deleteAddressBodySchema,
updateAddressBodySchema,
} from '../../../schemas/customer'
import { getCartBodySchema } from '../../../schemas/cart'
const customerShippingEndpoint: GetAPISchema<
any,
@ -15,6 +21,7 @@ const customerShippingEndpoint: GetAPISchema<
PUT: handlers['updateItem'],
DELETE: handlers['removeItem'],
})
const { cookies } = req
// Cart id might be usefull for anonymous shopping
@ -22,25 +29,25 @@ const customerShippingEndpoint: GetAPISchema<
// Return customer addresses
if (req.method === 'GET') {
const body = { cartId }
const body = getCartBodySchema.parse({ cartId })
return handlers['getAddresses']({ ...ctx, body })
}
// Create or add an item to customer addresses list
if (req.method === 'POST') {
const body = { ...req.body, cartId }
const body = addAddressBodySchema.parse({ ...req.body, cartId })
return handlers['addItem']({ ...ctx, body })
}
// Update item in customer addresses list
if (req.method === 'PUT') {
const body = { ...req.body, cartId }
const body = updateAddressBodySchema.parse({ ...req.body, cartId })
return handlers['updateItem']({ ...ctx, body })
}
// Remove an item from customer addresses list
if (req.method === 'DELETE') {
const body = { ...req.body, cartId }
const body = deleteAddressBodySchema.parse({ ...req.body, cartId })
return handlers['removeItem']({ ...ctx, body })
}
}

View File

@ -3,6 +3,12 @@ import type { GetAPISchema } from '../..'
import validateHandlers from '../../utils/validate-handlers'
import {
addCardBodySchema,
deleteCardBodySchema,
updateCardBodySchema,
} from '../../../schemas/customer'
const customerCardEndpoint: GetAPISchema<
any,
CustomerCardSchema
@ -28,19 +34,19 @@ const customerCardEndpoint: GetAPISchema<
// Create or add an item to customer cards
if (req.method === 'POST') {
const body = { ...req.body, cartId }
const body = addCardBodySchema.parse({ ...req.body, cartId })
return handlers['addItem']({ ...ctx, body })
}
// Update item in customer cards
if (req.method === 'PUT') {
const body = { ...req.body, cartId }
const body = updateCardBodySchema.parse({ ...req.body, cartId })
return handlers['updateItem']({ ...ctx, body })
}
// Remove an item from customer cards
if (req.method === 'DELETE') {
const body = { ...req.body, cartId }
const body = deleteCardBodySchema.parse({ ...req.body, cartId })
return handlers['removeItem']({ ...ctx, body })
}
}

View File

@ -1,7 +1,7 @@
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
import type { APIProvider, CommerceAPI } from '..'
import { getErrorResponse } from '../utils/errors'
import { normalizeError } from '../utils/errors'
/**
* Handles the catch-all api endpoint for the Commerce API.
@ -62,7 +62,7 @@ export default function createEndpoints<P extends APIProvider>(
}
} catch (error) {
console.error(error)
const { status, data, errors } = getErrorResponse(error)
const { status, data, errors } = normalizeError(error)
res.status(status).json({
data,
errors,

View File

@ -2,6 +2,7 @@ import type { GetAPISchema } from '..'
import type { LoginSchema } from '../../types/login'
import validateHandlers from '../utils/validate-handlers'
import { loginBodySchema } from '../../schemas/auth'
const loginEndpoint: GetAPISchema<any, LoginSchema>['endpoint']['handler'] = (
ctx
@ -13,7 +14,7 @@ const loginEndpoint: GetAPISchema<any, LoginSchema>['endpoint']['handler'] = (
GET: handlers['login'],
})
const body = req.body ?? {}
const body = loginBodySchema.parse(req.body)
return handlers['login']({ ...ctx, body })
}

View File

@ -1,6 +1,7 @@
import type { GetAPISchema } from '..'
import type { LogoutSchema } from '../../types/logout'
import { logoutBodySchema } from '../../schemas/auth'
import validateHandlers from '../utils/validate-handlers'
const logoutEndpoint: GetAPISchema<any, LogoutSchema>['endpoint']['handler'] = (
@ -11,8 +12,12 @@ const logoutEndpoint: GetAPISchema<any, LogoutSchema>['endpoint']['handler'] = (
validateHandlers(req, res, {
GET: handlers['logout'],
})
const redirectTo = req.query.redirect_to
const body = typeof redirectTo === 'string' ? { redirectTo } : {}
const body = logoutBodySchema.parse(
typeof redirectTo === 'string' ? { redirectTo } : {}
)
return handlers['logout']({ ...ctx, body })
}

View File

@ -1,6 +1,8 @@
import type { GetAPISchema } from '..'
import type { SignupSchema } from '../../types/signup'
import { signupBodySchema } from '../../schemas/auth'
import validateHandlers from '../utils/validate-handlers'
const signupEndpoint: GetAPISchema<any, SignupSchema>['endpoint']['handler'] = (
@ -14,7 +16,7 @@ const signupEndpoint: GetAPISchema<any, SignupSchema>['endpoint']['handler'] = (
const { cookies } = req
const cartId = cookies[config.cartCookie]
const body = { ...req.body, cartId }
const body = signupBodySchema.parse({ ...req.body, cartId })
return handlers['signup']({ ...ctx, body })
}

View File

@ -3,6 +3,12 @@ import type { WishlistSchema } from '../../types/wishlist'
import validateHandlers from '../utils/validate-handlers'
import {
getWishlistBodySchema,
addItemBodySchema,
removeItemBodySchema,
} from '../../schemas/whishlist'
const wishlistEndpoint: GetAPISchema<
any,
WishlistSchema
@ -20,22 +26,22 @@ const wishlistEndpoint: GetAPISchema<
// Return current wishlist info
if (req.method === 'GET') {
const body = {
const body = getWishlistBodySchema.parse({
customerToken,
includeProducts: !!req.query.products,
}
})
return handlers['getWishlist']({ ...ctx, body })
}
// Add an item to the wishlist
if (req.method === 'POST') {
const body = { ...req.body, customerToken }
const body = addItemBodySchema.parse({ ...req.body, customerToken })
return handlers['addItem']({ ...ctx, body })
}
// Remove an item from the wishlist
if (req.method === 'DELETE') {
const body = { ...req.body, customerToken }
const body = removeItemBodySchema.parse({ ...req.body, customerToken })
return handlers['removeItem']({ ...ctx, body })
}
}

View File

@ -1,7 +1,5 @@
import { ZodError } from 'zod'
import type { Response } from '@vercel/fetch'
import { CommerceError } from '../../utils/errors'
import { ZodError } from 'zod'
export class CommerceAPIError extends Error {
status: number
@ -24,7 +22,14 @@ export class CommerceNetworkError extends Error {
}
}
export const getErrorResponse = (error: unknown) => {
export const normalizeZodIssues = (issues: ZodError['issues']) =>
issues.map((e, index) => ({
message: `Error #${index + 1} ${
e.path.length > 0 ? `Path: ${e.path.join('.')}, ` : ''
}Code: ${e.code}, Message: ${e.message}`,
}))
export const normalizeError = (error: unknown) => {
if (error instanceof CommerceAPIError) {
return {
status: error.status || 500,
@ -39,7 +44,7 @@ export const getErrorResponse = (error: unknown) => {
return {
status: 400,
data: null,
errors: error.issues,
errors: normalizeZodIssues(error.issues),
}
}
@ -49,25 +54,3 @@ export const getErrorResponse = (error: unknown) => {
errors: [{ message: 'An unexpected error ocurred' }],
}
}
export const getOperationError = (operation: string, error: unknown) => {
if (error instanceof ZodError) {
return new CommerceError({
code: 'SCHEMA_VALIDATION_ERROR',
message:
`The ${operation} operation returned invalid data and has ${
error.issues.length
} parse ${error.issues.length === 1 ? 'error' : 'errors'}: \n` +
error.issues
.map(
(e, index) =>
`Error #${index + 1} ${
e.path.length > 0 ? `Path: ${e.path.join('.')}, ` : ''
}Code: ${e.code}, Message: ${e.message}`
)
.join('\n'),
})
}
return error
}

View File

@ -3,7 +3,6 @@ import isAllowedMethod, { HTTP_METHODS } from './is-allowed-method'
import { APIHandler } from './types'
/**
* Checks if the request method is allowed
* @throws Error if the method is not allowed
*/
export default function validateHandlers(

View File

@ -0,0 +1,18 @@
import { z } from 'zod'
export const loginBodySchema = z.object({
redirectTo: z.string().optional(),
email: z.string().email(),
password: z.string(),
})
export const logoutBodySchema = z.object({
redirectTo: z.string().optional(),
})
export const signupBodySchema = z.object({
firstName: z.string(),
lastName: z.string(),
email: z.string(),
password: z.string(),
})

View File

@ -0,0 +1,63 @@
import { z } from 'zod'
export const getCustomerAddressBodySchema = z.object({
cartId: z.string(),
})
export const addressFieldsSchema = z.object({
type: z.string(),
firstName: z.string(),
lastName: z.string(),
company: z.string(),
streetNumber: z.string(),
apartments: z.string(),
zipCode: z.string(),
city: z.string(),
country: z.string(),
})
export const addAddressBodySchema = z.object({
cartId: z.string(),
item: addressFieldsSchema,
})
export const updateAddressBodySchema = z.object({
cartId: z.string(),
itemId: z.string(),
item: addressFieldsSchema,
})
export const deleteAddressBodySchema = z.object({
cartId: z.string(),
itemId: z.string(),
})
export const cardFieldsSchema = z.object({
cardHolder: z.string(),
cardNumber: z.string(),
cardExpireDate: z.string(),
cardCvc: z.string(),
firstName: z.string(),
lastName: z.string(),
company: z.string(),
streetNumber: z.string(),
zipCode: z.string(),
city: z.string(),
country: z.string(),
})
export const addCardBodySchema = z.object({
cartId: z.string(),
item: cardFieldsSchema,
})
export const updateCardBodySchema = z.object({
cartId: z.string(),
itemId: z.string(),
item: cardFieldsSchema,
})
export const deleteCardBodySchema = z.object({
cartId: z.string(),
itemId: z.string(),
})

View File

@ -0,0 +1,27 @@
import { z } from 'zod'
export const getWishlistBodySchema = z.object({
customerAccessToken: z.string(),
includeProducts: z.boolean(),
})
export const wishlistItemBodySchema = z.object({
productId: z.string(),
variantId: z.string(),
})
export const addItemBodySchema = z.object({
cartId: z.string().optional(),
item: wishlistItemBodySchema,
})
export const updateItemBodySchema = z.object({
cartId: z.string(),
itemId: z.string(),
item: wishlistItemBodySchema,
})
export const removeItemBodySchema = z.object({
cartId: z.string(),
itemId: z.string(),
})

View File

@ -1,6 +1,6 @@
import type { CommercejsAPI } from '..'
import handleEndpoints from '@vercel/commerce/api/endpoints'
import createEndpoints from '@vercel/commerce/api/endpoints'
import login from './login'
import checkout from './checkout'
@ -10,7 +10,6 @@ const endpoints = {
checkout,
}
const handler = (commerce: CommercejsAPI) =>
handleEndpoints(commerce, endpoints)
export default handler
export default function commercejsAPI(commerce: CommercejsAPI) {
return createEndpoints(commerce, endpoints)
}

View File

@ -1,6 +1,6 @@
import type { KiboCommerceAPI } from '..'
import handleEndpoints from '@vercel/commerce/api/endpoints'
import createEndpoints from '@vercel/commerce/api/endpoints'
import cart from './cart'
import login from './login'
@ -20,7 +20,6 @@ const endpoints = {
'catalog/products': products,
}
const handler = (commerce: KiboCommerceAPI) =>
handleEndpoints(commerce, endpoints)
export default handler
export default function kiboCommerceAPI(commerce: KiboCommerceAPI) {
return createEndpoints(commerce, endpoints)
}

View File

@ -0,0 +1,8 @@
import createEndpoints from '@vercel/commerce/api/endpoints'
import type { LocalAPI } from '.'
const endpoints = {}
export default function localAPI(commerce: LocalAPI) {
return createEndpoints(commerce, endpoints)
}

View File

@ -1,6 +1,6 @@
import type { OrdercloudAPI } from '..'
import handleEndpoints from '@vercel/commerce/api/endpoints'
import createEndpoints from '@vercel/commerce/api/endpoints'
import cart from './cart'
import checkout from './checkout'
@ -16,7 +16,6 @@ const endpoints = {
'catalog/products': products,
}
const handler = (commerce: OrdercloudAPI) =>
handleEndpoints(commerce, endpoints)
export default handler
export default function ordercloudAPI(commerce: OrdercloudAPI) {
return createEndpoints(commerce, endpoints)
}

View File

@ -1,6 +1,6 @@
import vercelFetch from '@vercel/fetch'
import { FetcherError } from '@vercel/commerce/utils/errors'
import { CustomNodeJsGlobal } from '../../types/node';
import { CustomNodeJsGlobal } from '../../types/node'
import { OrdercloudConfig } from '../index'
@ -32,6 +32,8 @@ async function getToken({
// Get the body of it
const error = await authResponse.json()
console.log(JSON.stringify(error, null, 2))
// And return an error
throw new FetcherError({
errors: [{ message: error.error_description.Code }],
@ -144,12 +146,11 @@ export const createBuyerFetcher: (
body?: Record<string, unknown>,
fetchOptions?: Record<string, any>
) => {
const customGlobal = global as unknown as CustomNodeJsGlobal;
const customGlobal = global as unknown as CustomNodeJsGlobal
// Get provider config
const config = getConfig()
// If a token was passed, set it on global
if (fetchOptions?.token) {
customGlobal.token = fetchOptions.token

View File

@ -1,14 +1,12 @@
import type { Provider, SaleorAPI } from '..'
import handleEndpoints from '@vercel/commerce/api/endpoints'
import createEndpoints from '@vercel/commerce/api/endpoints'
import checkout from './checkout'
const endpoints = {
checkout,
}
const handler = (commerce: SaleorAPI) =>
handleEndpoints<Provider>(commerce, endpoints)
export default handler
export default function saleorAPI(commerce: SaleorAPI) {
return createEndpoints<Provider>(commerce, endpoints)
}

View File

@ -1,6 +1,6 @@
import type { Provider, SFCCProviderAPI } from '..'
import handleEndpoints from '@vercel/commerce/api/endpoints'
import createEndpoints from '@vercel/commerce/api/endpoints'
import products from './catalog/products'
@ -8,7 +8,6 @@ const endpoints = {
'catalog/products': products,
}
const handler = (commerce: SFCCProviderAPI) =>
handleEndpoints(commerce, endpoints)
export default handler
export default function sfccApi(commerce: SFCCProviderAPI) {
return createEndpoints(commerce, endpoints)
}

View File

@ -1,12 +1,12 @@
import type { Provider, ShopifyAPI } from '..'
import handleEndopints from '@vercel/commerce/api/endpoints'
import createEndpoints from '@vercel/commerce/api/endpoints'
import checkout from './checkout'
const endpoints = {
checkout,
}
const handler = (commerce: ShopifyAPI) =>
handleEndopints<Provider>(commerce, endpoints)
export default handler
export default function shopifyAPI(commerce: ShopifyAPI) {
return createEndpoints<Provider>(commerce, endpoints)
}

View File

@ -1,12 +1,11 @@
import type { SpreeApiProvider, SpreeApi } from '..'
import handleEndopints from '@vercel/commerce/api/endpoints'
import createEndpoints from '@vercel/commerce/api/endpoints'
import checkout from './checkout'
const endpoints = {
checkout,
}
const handler = (commerce: SpreeApi) =>
handleEndopints<SpreeApiProvider>(commerce, endpoints)
export default handler
export default function spreeAPI(commerce: SpreeApi) {
return createEndpoints<SpreeApiProvider>(commerce, endpoints)
}

View File

@ -1,12 +1,12 @@
import type { Provider, SwellAPI } from '..'
import handleEndopints from '@vercel/commerce/api/endpoints'
import createEndpoints from '@vercel/commerce/api/endpoints'
import checkout from './checkout'
const endpoints = {
checkout,
}
const handler = (commerce: SwellAPI) =>
handleEndopints<Provider>(commerce, endpoints)
export default handler
export default function handler(commerce: SwellAPI) {
return createEndpoints<Provider>(commerce, endpoints)
}

View File

@ -1,12 +1,12 @@
import type { Provider, VendureAPI } from '..'
import handleEndopints from '@vercel/commerce/api/endpoints'
import createEndpoints from '@vercel/commerce/api/endpoints'
import checkout from './checkout'
const endpoints = {
checkout,
}
const handler = (commerce: VendureAPI) =>
handleEndopints<Provider>(commerce, endpoints)
export default handler
export default function vendureAPI(commerce: VendureAPI) {
return createEndpoints<Provider>(commerce, endpoints)
}

View File

@ -27,6 +27,7 @@ import {
getDesignerPath,
useSearchMeta,
} from '@lib/search'
import ErrorMessage from './ui/ErrorMessage'
export default function Search({ categories, brands }: SearchPropsType) {
const [activeFilter, setActiveFilter] = useState('')
@ -46,7 +47,7 @@ export default function Search({ categories, brands }: SearchPropsType) {
(b: any) => getSlug(b.node.path) === `brands/${brand}`
)?.node
const { data } = useSearch({
const { data, error } = useSearch({
search: typeof q === 'string' ? q : '',
categoryId: activeCategory?.id,
brandId: (activeBrand as any)?.entityId,
@ -54,6 +55,10 @@ export default function Search({ categories, brands }: SearchPropsType) {
locale,
})
if (error) {
return <ErrorMessage error={error} />
}
const handleClick = (event: any, filter: string) => {
if (filter !== activeFilter) {
setToggleFilter(true)

View File

@ -0,0 +1,28 @@
import type { FC } from 'react'
interface ErrorMessageProps {
error: {
message: string
code?: string
errors?: {
message: string
}[]
}
}
const ErrorMessages: FC<ErrorMessageProps> = ({ error }) => {
return (
<div className="flex flex-col text-red p-5 m-5 border border-solid border-red">
<span>{error.message}</span>
{error.errors && error.errors?.length > 0 && (
<ul>
{error.errors.map(({ message }, index) => (
<li key={index}>{message}</li>
))}
</ul>
)}
</div>
)
}
export default ErrorMessages

View File

@ -0,0 +1 @@
export { default } from './ErrorMessage'

View File

@ -18,7 +18,7 @@ module.exports = withCommerceConfig({
return [
(isBC || isShopify || isSwell || isVendure || isSaleor) && {
source: '/checkout',
destination: '/api/checkout',
destination: '/api/commerce/checkout',
},
// The logout is also an action so this route is not required, but it's also another way
// you can allow a logout!