From 70873f3fd137459c85367db6e2733d37286bd5ab Mon Sep 17 00:00:00 2001 From: Victor Gerbrands Date: Mon, 7 Aug 2023 15:35:45 +0200 Subject: [PATCH] fix: revalidation and caching --- .env.example | 3 +- lib/constants.ts | 2 +- lib/medusa/index.ts | 109 +++++++++++++++++++++++++++++++++----------- 3 files changed, 85 insertions(+), 29 deletions(-) diff --git a/.env.example b/.env.example index 9a9a20f1d..80f5ffa6a 100644 --- a/.env.example +++ b/.env.example @@ -3,4 +3,5 @@ TWITTER_SITE="https://medusajs.com/" SITE_NAME="Medusa Store" NEXT_PUBLIC_MEDUSA_BACKEND_API="http://localhost:9000" NEXT_PUBLIC_VERCEL_URL="http://localhost:3000" -MEDUSA_API_KEY="" \ No newline at end of file +MEDUSA_API_KEY="" +MEDUSA_REVALIDATION_SECRET="supersecret123" \ No newline at end of file diff --git a/lib/constants.ts b/lib/constants.ts index e41e8858a..6ca7479a5 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -21,7 +21,7 @@ export const sorting: SortFilterItem[] = [ ]; export const TAGS = { - collections: 'collections', + categories: 'categories', products: 'products' }; diff --git a/lib/medusa/index.ts b/lib/medusa/index.ts index 2ea99b5f2..8b03af11d 100644 --- a/lib/medusa/index.ts +++ b/lib/medusa/index.ts @@ -27,23 +27,32 @@ import { const ENDPOINT = process.env.NEXT_PUBLIC_MEDUSA_BACKEND_API ?? 'http://localhost:9000'; const MEDUSA_API_KEY = process.env.MEDUSA_API_KEY ?? ''; -const REVALIDATE_WINDOW = parseInt(process.env.REVALIDATE_WINDOW ?? `${60 * 15}`); // 15 minutes -export default async function medusaRequest( - method: string, - path = '', - payload?: Record | undefined -) { +export default async function medusaRequest({ + cache = 'force-cache', + method, + path, + payload, + tags +}: { + cache?: RequestCache; + method: string; + path: string; + payload?: Record | undefined; + tags?: string[]; +}) { const options: RequestInit = { method, headers: { 'Content-Type': 'application/json', 'x-publishable-key': MEDUSA_API_KEY - } + }, + cache, + ...(tags && { next: { tags } }) }; - if (!path.includes('/carts')) { - options.next = { revalidate: REVALIDATE_WINDOW }; + if (path.includes('/carts')) { + options.cache = 'no-cache'; } if (payload) { @@ -293,7 +302,7 @@ const reshapeCategory = (category: ProductCategory): ProductCollection => { }; export async function createCart(): Promise { - const res = await medusaRequest('POST', '/carts', {}); + const res = await medusaRequest({ method: 'POST', path: '/carts' }); return reshapeCart(res.body.cart); } @@ -301,15 +310,24 @@ export async function addToCart( cartId: string, lineItem: { variantId: string; quantity: number } ): Promise { - const res = await medusaRequest('POST', `/carts/${cartId}/line-items`, { - variant_id: lineItem?.variantId, - quantity: lineItem?.quantity + const res = await medusaRequest({ + method: 'POST', + path: `/carts/${cartId}/line-items`, + payload: { + variant_id: lineItem?.variantId, + quantity: lineItem?.quantity + }, + tags: ['cart'] }); return reshapeCart(res.body.cart); } export async function removeFromCart(cartId: string, lineItemId: string): Promise { - const res = await medusaRequest('DELETE', `/carts/${cartId}/line-items/${lineItemId}`); + const res = await medusaRequest({ + method: 'DELETE', + path: `/carts/${cartId}/line-items/${lineItemId}`, + tags: ['cart'] + }); return reshapeCart(res.body.cart); } @@ -317,14 +335,19 @@ export async function updateCart( cartId: string, { lineItemId, quantity }: { lineItemId: string; quantity: number } ): Promise { - const res = await medusaRequest('POST', `/carts/${cartId}/line-items/${lineItemId}`, { - quantity + const res = await medusaRequest({ + method: 'POST', + path: `/carts/${cartId}/line-items/${lineItemId}`, + payload: { + quantity + }, + tags: ['cart'] }); return reshapeCart(res.body.cart); } export async function getCart(cartId: string): Promise { - const res = await medusaRequest('GET', `/carts/${cartId}`); + const res = await medusaRequest({ method: 'GET', path: `/carts/${cartId}`, tags: ['cart'] }); const cart = res.body.cart; if (!cart) { @@ -335,7 +358,11 @@ export async function getCart(cartId: string): Promise { } export async function getCategories(): Promise { - const res = await medusaRequest('GET', '/product-categories'); + const res = await medusaRequest({ + method: 'GET', + path: '/product-categories', + tags: ['categories'] + }); // Reshape categories and hide categories starting with 'hidden' const categories = res.body.product_categories @@ -346,7 +373,11 @@ export async function getCategories(): Promise { } export async function getCategory(handle: string): Promise { - const res = await medusaRequest('GET', `/product-categories?handle=${handle}&expand=products`); + const res = await medusaRequest({ + method: 'GET', + path: `/product-categories?handle=${handle}&expand=products`, + tags: ['categories', 'products'] + }); return res.body.product_categories[0]; } @@ -355,7 +386,11 @@ export async function getCategoryProducts( reverse: boolean, sortKey: string ): Promise { - const res = await medusaRequest('GET', `/product-categories?handle=${handle}`); + const res = await medusaRequest({ + method: 'GET', + path: `/product-categories?handle=${handle}`, + tags: ['categories'] + }); if (!res) { return []; @@ -369,7 +404,11 @@ export async function getCategoryProducts( } export async function getProduct(handle: string): Promise { - const res = await medusaRequest('GET', `/products?handle=${handle}&limit=1`); + const res = await medusaRequest({ + method: 'GET', + path: `/products?handle=${handle}&limit=1`, + tags: ['products'] + }); const product = res.body.products[0]; return reshapeProduct(product); } @@ -388,15 +427,23 @@ export async function getProducts({ let res; if (query) { - res = await medusaRequest('GET', `/products?q=${query}&limit=100`); + res = await medusaRequest({ + method: 'GET', + path: `/products?q=${query}&limit=100`, + tags: ['products'] + }); } else if (categoryId) { - res = await medusaRequest('GET', `/products?category_id[]=${categoryId}&limit=100`); + res = await medusaRequest({ + method: 'GET', + path: `/products?category_id[]=${categoryId}&limit=100`, + tags: ['products'] + }); } else { - res = await medusaRequest('GET', `/products?limit=100`); + res = await medusaRequest({ method: 'GET', path: `/products?limit=100`, tags: ['products'] }); } if (!res) { - console.log("Couldn't fetch products"); + console.error("Couldn't fetch products"); return []; } @@ -441,19 +488,27 @@ export async function getMenu(menu: string): Promise { // This is called from `app/api/revalidate.ts` so providers can control revalidation logic. export async function revalidate(req: NextRequest): Promise { - const collectionWebhooks = ['collections/create', 'collections/delete', 'collections/update']; + // We always need to respond with a 200 status code to Medusa, + // otherwise it will continue to retry the request. + const collectionWebhooks = ['categories/create', 'categories/delete', 'categories/update']; const productWebhooks = ['products/create', 'products/delete', 'products/update']; const topic = headers().get('x-medusa-topic') || 'unknown'; + const secret = req.nextUrl.searchParams.get('secret'); const isCollectionUpdate = collectionWebhooks.includes(topic); const isProductUpdate = productWebhooks.includes(topic); + if (!secret || secret !== process.env.MEDUSA_REVALIDATION_SECRET) { + console.error('Invalid revalidation secret.'); + return NextResponse.json({ status: 200 }); + } + if (!isCollectionUpdate && !isProductUpdate) { // We don't need to revalidate anything for any other topics. return NextResponse.json({ status: 200 }); } if (isCollectionUpdate) { - revalidateTag(TAGS.collections); + revalidateTag(TAGS.categories); } if (isProductUpdate) {