Files
supabase/apps/docs/app/api/revalidate/route.ts
T
Ivan VasilovandCharis Lam 0cb08341f6 chore: Bump nextjs for the docs app (#35333)
* Bump all versions of postcss to 8.5.3.

* Run next/codemod on the docs app.

* Move two experimental flags into stable. Add next-mdx-remote as a transpiled package.

* Add extra folders to the clean command in docs.

* Fix type errors in docs test.

* Run prettier on the new files.

* remove turbopack, fix fetch revalidation, fix metadata awaits

Couple of minor fixes:
- Turbopack doesn't work in dev because of known MDX loader limitations
(cannot load functions in MDX plugin config)
- Fetches not cached by default anymore in Next 15 so need to manually
cache the ones we need
- Missing a few awaits for metadata generation with page params

* Bump the graphiql version because headlessui/react is not building with Next 15.

---------

Co-authored-by: Charis Lam <26616127+charislam@users.noreply.github.com>
2025-05-08 09:27:22 +00:00

118 lines
3.5 KiB
TypeScript

import { createClient } from '@supabase/supabase-js'
import { type Database } from 'common'
import { revalidateTag } from 'next/cache'
import { headers } from 'next/headers'
import { type NextRequest } from 'next/server'
import { z } from 'zod'
import { VALID_REVALIDATION_TAGS } from '~/features/helpers.fetch'
enum AuthorizationLevel {
Unauthorized,
Basic,
Override,
}
const requestBodySchema = z.object({
tags: z.array(z.enum(VALID_REVALIDATION_TAGS)),
})
export const POST = handleError(_handleRevalidateRequest)
export async function _handleRevalidateRequest(request: NextRequest) {
const requestHeaders = await headers()
const authorization = requestHeaders.get('Authorization')
if (!authorization) {
return new Response('Missing Authorization header', { status: 401 })
}
const basicKeys = process.env.DOCS_REVALIDATION_KEYS?.split(/\s*,\s*/) ?? []
const overrideKeys = process.env.DOCS_REVALIDATION_OVERRIDE_KEYS?.split(/\s*,\s*/) ?? []
if (basicKeys.length === 0 && overrideKeys.length === 0) {
console.error('No keys configured for revalidation')
return new Response('Internal server error', {
status: 500,
})
}
let authorizationLevel = AuthorizationLevel.Unauthorized
const token = authorization.replace(/^Bearer /, '')
if (overrideKeys.includes(token)) {
authorizationLevel = AuthorizationLevel.Override
} else if (basicKeys.includes(token)) {
authorizationLevel = AuthorizationLevel.Basic
}
if (authorizationLevel === AuthorizationLevel.Unauthorized) {
return new Response('Invalid Authorization header', { status: 401 })
}
let result: z.infer<typeof requestBodySchema>
try {
result = requestBodySchema.parse(await request.json())
} catch (error) {
console.error(error)
return new Response(
'Malformed request body: should be a JSON object with a "tags" array of strings.',
{ status: 400 }
)
}
const supabaseAdmin = createClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SECRET_KEY!
)
if (authorizationLevel === AuthorizationLevel.Basic) {
const { data: lastRevalidation, error } = await supabaseAdmin.rpc(
'get_last_revalidation_for_tags',
{
tags: result.tags,
}
)
if (error) {
console.error(error)
return new Response('Internal server error', { status: 500 })
}
const sixHoursAgo = new Date()
sixHoursAgo.setHours(sixHoursAgo.getHours() - 6)
if (lastRevalidation?.some((revalidation) => new Date(revalidation.created_at) > sixHoursAgo)) {
return new Response(
'Your request includes a tag that has been revalidated within the last 6 hours. You can override this limit by authenticating with Override permissions.',
{
status: 429,
}
)
}
}
const { error } = await supabaseAdmin
.from('validation_history')
.insert(result.tags.map((tag) => ({ tag })))
if (error) {
console.error('Failed to update revalidation table: %o', error)
}
result.tags.forEach((tag) => {
revalidateTag(tag)
})
return new Response(null, {
status: 204,
headers: {
'Cache-Control': 'no-cache',
},
})
}
function handleError(handleRequest: (request: NextRequest) => Promise<Response>) {
return async function (request: NextRequest) {
try {
const response = await handleRequest(request)
return response
} catch (error) {
console.error(error)
return new Response('Internal server error', { status: 500 })
}
}
}