TypeScript 97.5%
SQL 1.4%
Python 0.8%
1import "server-only";2import { getDb, providerHealth, fetchRequests, statusIncidents, sql, gte, and, ne, or, isNull, isNotNull, desc } from "@fetcha/db";3import { internalApi } from "@/lib/api";45export type ComponentState = "operational" | "degraded" | "outage" | "not-launched" | "unknown";67export type DayPoint = { day: string; state: ComponentState | "no-data"; successRate: number | null; total: number };89export type StatusComponent = {10 key: string;11 name: string;12 description: string;13 state: ComponentState;14 detail?: string;15 history: DayPoint[]; // 30 entries, oldest first16};1718export type Incident = {19 id: string;20 component: string;21 title: string;22 body: string | null;23 severity: string;24 startedAt: Date;25 resolvedAt: Date | null;26};2728export type StatusSnapshot = {29 generatedAt: Date;30 overall: ComponentState;31 components: StatusComponent[];32 successRate24h: number | null;33 requests24h: number;34 openIncidents: Incident[];35 resolvedIncidents: Incident[];36 dbReachable: boolean;37};3839const EXCLUDED_ERROR_CODES = ["INVALID_REQUEST", "URL_NOT_ALLOWED", "USAGE_LIMIT_REACHED"] as const;40const HISTORY_DAYS = 30;4142const COMPONENT_DEFS: Array<{ key: string; name: string; description: string }> = [43 { key: "api", name: "API", description: "POST /v1/fetch, sessions, usage and authentication endpoints." },44 { key: "dashboard", name: "Dashboard", description: "Web console, playground and request logs." },45 { key: "proxy-network", name: "Proxy Network", description: "Aggregate upstream capacity across all network classes." },46 { key: "residential-network", name: "Residential Network", description: "Residential exit nodes with geo targeting." },47 { key: "browser-network", name: "Browser Network", description: "Managed headless browser execution." },48 { key: "authentication", name: "Authentication", description: "Sign-in, sign-up, email verification and API key validation." },49 { key: "billing", name: "Billing", description: "Checkout, invoices and self-serve plan changes." },50];5152function utcDayKey(d: Date): string {53 return d.toISOString().slice(0, 10);54}5556function healthToState(rows: Array<{ status: string }>): ComponentState {57 if (rows.some((r) => r.status === "healthy")) return "operational";58 if (rows.some((r) => r.status === "degraded")) return "degraded";59 return "outage";60}6162function rateToState(rate: number | null, total: number): DayPoint["state"] {63 if (rate === null || total === 0) return "no-data";64 if (rate >= 0.99) return "operational";65 if (rate >= 0.95) return "degraded";66 return "outage";67}6869function worst(states: ComponentState[]): ComponentState {70 const rank: Record<ComponentState, number> = { outage: 4, degraded: 3, unknown: 2, operational: 1, "not-launched": 0 };71 return states.reduce<ComponentState>((acc, s) => (rank[s] > rank[acc] ? s : acc), "operational");72}7374function emptyHistory(state: DayPoint["state"]): DayPoint[] {75 const out: DayPoint[] = [];76 const today = new Date();77 for (let i = HISTORY_DAYS - 1; i >= 0; i--) {78 const d = new Date(Date.UTC(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate() - i));79 out.push({ day: utcDayKey(d), state, successRate: null, total: 0 });80 }81 return out;82}8384/** Marks days overlapped by incidents for a given component as degraded/outage; everything else stays as provided. */85function overlayIncidents(history: DayPoint[], incidents: Incident[], componentKey: string, componentName: string): DayPoint[] {86 const mine = incidents.filter((i) => {87 const c = i.component.toLowerCase();88 return c === componentKey || c === componentName.toLowerCase() || c === componentKey.replace(/-/g, "_");89 });90 if (mine.length === 0) return history;91 return history.map((p) => {92 const dayStart = new Date(`${p.day}T00:00:00.000Z`).getTime();93 const dayEnd = dayStart + 86_400_000;94 const hit = mine.filter((i) => i.startedAt.getTime() < dayEnd && (i.resolvedAt ? i.resolvedAt.getTime() : Date.now()) >= dayStart);95 if (hit.length === 0) return p;96 const major = hit.some((i) => ["major", "critical"].includes(i.severity.toLowerCase()));97 return { ...p, state: major ? "outage" : "degraded" };98 });99}100101export async function getStatusSnapshot(): Promise<StatusSnapshot> {102 const now = new Date();103 const since24h = new Date(now.getTime() - 24 * 3600 * 1000);104 const since30d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - (HISTORY_DAYS - 1)));105106 let dbReachable = true;107 let healthRows: Array<{ network: string; status: string }> = [];108 let successRate24h: number | null = null;109 let requests24h = 0;110 let daily: Map<string, { total: number; ok: number }> = new Map();111 let openIncidents: Incident[] = [];112 let resolvedIncidents: Incident[] = [];113114 // --- Provider health (never expose provider names; we only read network + status).115 try {116 const db = getDb();117 healthRows = await db118 .select({ network: providerHealth.network, status: providerHealth.status })119 .from(providerHealth)120 .where(gte(providerHealth.checkedAt, since24h))121 .orderBy(desc(providerHealth.checkedAt))122 .limit(500);123 } catch {124 dbReachable = false;125 }126127 const excludeErrors = or(isNull(fetchRequests.errorCode), sql`${fetchRequests.errorCode} not in (${sql.join(EXCLUDED_ERROR_CODES.map((c) => sql`${c}`), sql`, `)})`);128129 // --- 24 h success rate.130 if (dbReachable) {131 try {132 const db = getDb();133 const [row] = await db134 .select({135 total: sql<number>`count(*)::int`,136 ok: sql<number>`count(*) filter (where ${fetchRequests.status} = 'success')::int`,137 })138 .from(fetchRequests)139 .where(and(gte(fetchRequests.createdAt, since24h), ne(fetchRequests.status, "pending"), excludeErrors));140 const total = Number(row?.total ?? 0);141 const ok = Number(row?.ok ?? 0);142 requests24h = total;143 successRate24h = total > 0 ? ok / total : null;144 } catch {145 dbReachable = false;146 }147 }148149 // --- 30 day daily buckets.150 if (dbReachable) {151 try {152 const db = getDb();153 const rows = await db154 .select({155 day: sql<string | Date>`date_trunc('day', ${fetchRequests.createdAt} at time zone 'UTC')`.as("day"),156 total: sql<number>`count(*)::int`,157 ok: sql<number>`count(*) filter (where ${fetchRequests.status} = 'success')::int`,158 })159 .from(fetchRequests)160 .where(and(gte(fetchRequests.createdAt, since30d), ne(fetchRequests.status, "pending"), excludeErrors))161 .groupBy(sql`1`)162 .orderBy(sql`1`);163 daily = new Map(164 rows.map((r) => {165 const d = r.day instanceof Date ? r.day : new Date(r.day);166 return [utcDayKey(d), { total: Number(r.total), ok: Number(r.ok) }];167 }),168 );169 } catch {170 dbReachable = false;171 }172 }173174 // --- Incidents.175 if (dbReachable) {176 try {177 const db = getDb();178 const toIncident = (r: typeof statusIncidents.$inferSelect): Incident => ({179 id: r.id,180 component: r.component,181 title: r.title,182 body: r.body,183 severity: r.severity,184 startedAt: new Date(r.startedAt),185 resolvedAt: r.resolvedAt ? new Date(r.resolvedAt) : null,186 });187 const open = await db.select().from(statusIncidents).where(isNull(statusIncidents.resolvedAt)).orderBy(desc(statusIncidents.startedAt)).limit(20);188 const resolved = await db.select().from(statusIncidents).where(isNotNull(statusIncidents.resolvedAt)).orderBy(desc(statusIncidents.startedAt)).limit(10);189 openIncidents = open.map(toIncident);190 resolvedIncidents = resolved.map(toIncident);191 } catch {192 // incidents are optional; keep whatever we have193 }194 }195196 // --- API readiness via the internal API.197 let apiState: ComponentState = "operational";198 let apiDetail: string | undefined;199 try {200 const ready = await internalApi.ready();201 const s = String(ready.status ?? "").toLowerCase();202 apiState = s === "ok" || s === "ready" || s === "healthy" ? "operational" : "degraded";203 if (apiState === "degraded") apiDetail = "Readiness check reports a degraded dependency.";204 } catch {205 apiState = "degraded";206 apiDetail = "Readiness check unreachable from the status page.";207 }208209 // --- Build the 30-day history from daily fetch success rates.210 const trafficHistory: DayPoint[] = emptyHistory("no-data").map((p) => {211 const b = daily.get(p.day);212 if (!b || b.total === 0) return p;213 const rate = b.ok / b.total;214 return { day: p.day, total: b.total, successRate: rate, state: rateToState(rate, b.total) };215 });216 const allIncidents = [...openIncidents, ...resolvedIncidents];217218 const residentialRows = healthRows.filter((r) => r.network === "residential");219 const residentialState: ComponentState = dbReachable ? healthToState(residentialRows) : "unknown";220 const proxyState: ComponentState = dbReachable ? healthToState(healthRows) : "unknown";221 const dashboardState: ComponentState = dbReachable ? "operational" : "degraded";222 const authState: ComponentState = dbReachable ? "operational" : "degraded";223224 const rate24Detail = successRate24h === null ? "No requests in the last 24 hours." : `${(successRate24h * 100).toFixed(2)}% success over ${requests24h.toLocaleString("en-US")} requests (24 h).`;225226 const components: StatusComponent[] = COMPONENT_DEFS.map((def) => {227 switch (def.key) {228 case "api":229 return { ...def, state: apiState, detail: apiDetail ?? rate24Detail, history: overlayIncidents(trafficHistory, allIncidents, def.key, def.name) };230 case "dashboard":231 return { ...def, state: dashboardState, detail: dbReachable ? undefined : "Database unreachable while rendering this page.", history: overlayIncidents(emptyHistory("operational"), allIncidents, def.key, def.name) };232 case "proxy-network":233 return {234 ...def,235 state: proxyState,236 detail: !dbReachable ? "Health data unavailable." : healthRows.length === 0 ? "No health checks recorded in the last 24 hours." : undefined,237 history: overlayIncidents(trafficHistory, allIncidents, def.key, def.name),238 };239 case "residential-network":240 return {241 ...def,242 state: residentialState,243 detail: !dbReachable ? "Health data unavailable." : residentialRows.length === 0 ? "No residential health checks recorded in the last 24 hours." : rate24Detail,244 history: overlayIncidents(trafficHistory, allIncidents, def.key, def.name),245 };246 case "browser-network":247 return { ...def, state: "not-launched", detail: "Browser execution is not yet available (BROWSER_UNAVAILABLE).", history: emptyHistory("no-data") };248 case "authentication":249 return { ...def, state: authState, detail: dbReachable ? undefined : "Database unreachable while rendering this page.", history: overlayIncidents(emptyHistory("operational"), allIncidents, def.key, def.name) };250 case "billing":251 return { ...def, state: "not-launched", detail: "Checkout is not live. Upgrades by email to sales@fetcha.co.", history: emptyHistory("no-data") };252 default:253 return { ...def, state: "unknown", history: emptyHistory("no-data") };254 }255 });256257 const overall = worst(components.map((c) => c.state).filter((s) => s !== "not-launched"));258259 return { generatedAt: now, overall, components, successRate24h, requests24h, openIncidents, resolvedIncidents, dbReachable };260}261