route.ts108 lines · main
1import { IS_PLATFORM } from 'common'
2import { NextResponse } from 'next/server'
3
4import { InternalServerError } from '@/lib/api/apiHelpers'
5import { getActiveIncidents, type IncidentCache } from '@/lib/api/incident-status'
6import { createAdminClient } from '@/lib/api/briven-admin'
7
8/**
9 * Cache on CDN for 5 minutes
10 * Allow serving stale content for 1 minute while revalidating
11 */
12const CACHE_CONTROL_SETTINGS = 'public, s-maxage=300, stale-while-revalidate=60'
13
14async function fetchIncidentCache(incidentIds: Array<string>): Promise<Map<string, IncidentCache>> {
15 const cacheMap = new Map<string, IncidentCache>()
16
17 if (incidentIds.length === 0) return cacheMap
18
19 const briven = createAdminClient()
20
21 try {
22 const { data, error } = await briven
23 .from('incident_status_cache')
24 .select('incident_id, affected_regions, affects_project_creation')
25 .in('incident_id', incidentIds)
26
27 if (error) {
28 console.error('Failed to fetch incident_status_cache: %O', error)
29 return cacheMap
30 }
31
32 for (const row of data ?? []) {
33 cacheMap.set(row.incident_id, {
34 affected_regions: row.affected_regions ?? null,
35 affects_project_creation: row.affects_project_creation,
36 })
37 }
38 } catch (error) {
39 console.error('Unexpected error fetching incident_status_cache: %O', error)
40 }
41
42 return cacheMap
43}
44
45export async function OPTIONS() {
46 if (!IS_PLATFORM) return new Response(null, { status: 404 })
47 return new Response(null, {
48 status: 204,
49 headers: {
50 Allow: 'GET, HEAD, OPTIONS',
51 },
52 })
53}
54
55export async function HEAD() {
56 if (!IS_PLATFORM) return new Response(null, { status: 404 })
57 return new Response(null, {
58 status: 200,
59 headers: { 'Cache-Control': CACHE_CONTROL_SETTINGS },
60 })
61}
62
63export async function GET() {
64 if (!IS_PLATFORM) return new Response(null, { status: 404 })
65
66 try {
67 const allIncidents = await getActiveIncidents()
68
69 const bannerIncidents = allIncidents.filter(
70 (incident) =>
71 incident.impact !== 'maintenance' &&
72 incident.metadata?.dashboard_metadata?.show_banner === true
73 )
74
75 const cacheMap = await fetchIncidentCache(bannerIncidents.map((i) => i.id))
76
77 const enrichedIncidents = bannerIncidents.map((incident) => ({
78 ...incident,
79 cache: cacheMap.get(incident.id) ?? null,
80 }))
81
82 return NextResponse.json(enrichedIncidents, {
83 headers: { 'Cache-Control': CACHE_CONTROL_SETTINGS },
84 })
85 } catch (error) {
86 let errorCode = 500
87 const headers = new Headers()
88
89 if (error instanceof InternalServerError) {
90 if (typeof error.details?.status === 'number') errorCode = error.details.status
91 if (errorCode === 420) errorCode = 429
92 if (errorCode === 429 && typeof error.details?.retryAfter === 'string') {
93 headers.set('Retry-After', error.details.retryAfter)
94 }
95 console.error('Failed to fetch active StatusPage incidents: %O', {
96 message: error.message,
97 details: error.details,
98 })
99 } else {
100 console.error('Unexpected error fetching active StatusPage incidents: %O', error)
101 }
102
103 return NextResponse.json(
104 { error: 'Unable to fetch incidents at this time' },
105 { status: errorCode, headers }
106 )
107 }
108}