mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 18:05:11 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Refactor ## What is the current behavior? `/incident-status` is handled via Pages Router. ## What is the new behavior? `/incident-status` is handled via App Router, enabling use of Vercel Data Cache to cache the upstream fetch. ## Additional context Adding the first App Router route handler triggered `next typegen` (run as `pretypecheck`) to generate `.next/dev/types/validator.ts`, which imports all route files and expanded the type-checked graph. This surfaced pre-existing `null`-safety errors in: - `components/grid/SupabaseGrid.utils.ts` — `useSearchParams()` result - `components/layouts/ProjectLayout/UpgradingState/index.tsx` — `useSearchParams()` result - `pages/project/[ref]/sql/quickstarts.tsx` — `useParams()` result - `pages/project/[ref]/sql/templates.tsx` — `useParams()` result These are fixed with optional chaining. The `tsconfig.json` change (adding `.next/dev/types/**/*.ts` to `include`) is auto-generated by Next.js and committed as correct behavior.
109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import { IS_PLATFORM } from 'common'
|
|
import { NextResponse } from 'next/server'
|
|
|
|
import { InternalServerError } from '@/lib/api/apiHelpers'
|
|
import { getActiveIncidents, type IncidentCache } from '@/lib/api/incident-status'
|
|
import { createAdminClient } from '@/lib/api/supabase-admin'
|
|
|
|
/**
|
|
* Cache on CDN for 5 minutes
|
|
* Allow serving stale content for 1 minute while revalidating
|
|
*/
|
|
const CACHE_CONTROL_SETTINGS = 'public, s-maxage=300, stale-while-revalidate=60'
|
|
|
|
async function fetchIncidentCache(incidentIds: Array<string>): Promise<Map<string, IncidentCache>> {
|
|
const cacheMap = new Map<string, IncidentCache>()
|
|
|
|
if (incidentIds.length === 0) return cacheMap
|
|
|
|
const supabase = createAdminClient()
|
|
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('incident_status_cache')
|
|
.select('incident_id, affected_regions, affects_project_creation')
|
|
.in('incident_id', incidentIds)
|
|
|
|
if (error) {
|
|
console.error('Failed to fetch incident_status_cache: %O', error)
|
|
return cacheMap
|
|
}
|
|
|
|
for (const row of data ?? []) {
|
|
cacheMap.set(row.incident_id, {
|
|
affected_regions: row.affected_regions ?? null,
|
|
affects_project_creation: row.affects_project_creation,
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('Unexpected error fetching incident_status_cache: %O', error)
|
|
}
|
|
|
|
return cacheMap
|
|
}
|
|
|
|
export async function OPTIONS() {
|
|
if (!IS_PLATFORM) return new Response(null, { status: 404 })
|
|
return new Response(null, {
|
|
status: 204,
|
|
headers: {
|
|
Allow: 'GET, HEAD, OPTIONS',
|
|
},
|
|
})
|
|
}
|
|
|
|
export async function HEAD() {
|
|
if (!IS_PLATFORM) return new Response(null, { status: 404 })
|
|
return new Response(null, {
|
|
status: 200,
|
|
headers: { 'Cache-Control': CACHE_CONTROL_SETTINGS },
|
|
})
|
|
}
|
|
|
|
export async function GET() {
|
|
if (!IS_PLATFORM) return new Response(null, { status: 404 })
|
|
|
|
try {
|
|
const allIncidents = await getActiveIncidents()
|
|
|
|
const bannerIncidents = allIncidents.filter(
|
|
(incident) =>
|
|
incident.impact !== 'maintenance' &&
|
|
incident.metadata?.dashboard_metadata?.show_banner === true
|
|
)
|
|
|
|
const cacheMap = await fetchIncidentCache(bannerIncidents.map((i) => i.id))
|
|
|
|
const enrichedIncidents = bannerIncidents.map((incident) => ({
|
|
...incident,
|
|
cache: cacheMap.get(incident.id) ?? null,
|
|
}))
|
|
|
|
return NextResponse.json(enrichedIncidents, {
|
|
headers: { 'Cache-Control': CACHE_CONTROL_SETTINGS },
|
|
})
|
|
} catch (error) {
|
|
let errorCode = 500
|
|
const headers = new Headers()
|
|
|
|
if (error instanceof InternalServerError) {
|
|
if (typeof error.details?.status === 'number') errorCode = error.details.status
|
|
if (errorCode === 420) errorCode = 429
|
|
if (errorCode === 429 && typeof error.details?.retryAfter === 'string') {
|
|
headers.set('Retry-After', error.details.retryAfter)
|
|
}
|
|
console.error('Failed to fetch active StatusPage incidents: %O', {
|
|
message: error.message,
|
|
details: error.details,
|
|
})
|
|
} else {
|
|
console.error('Unexpected error fetching active StatusPage incidents: %O', error)
|
|
}
|
|
|
|
return NextResponse.json(
|
|
{ error: 'Unable to fetch incidents at this time' },
|
|
{ status: errorCode, headers }
|
|
)
|
|
}
|
|
}
|