Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/openstatusHQ/openstatus. ๐ซ Status page with uptime monitoring & API monitoring as code ๐ซ openstatus.dev
bun drizzle-orm monitoring monitoring-as-code nextjs observability on-call open-source shadcn-ui status-page statuspage synthetic-monitoring tinybird turso uptime uptime-checker uptime-monitor
Something went wrong. Try again.
5.8 kB ยท 196 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197import { monitorPeriodicitySchema } from "@openstatus/db/src/schema/constants";import * as Sentry from "@sentry/deno";import { Effect, Schedule } from "effect";import { Hono } from "hono";
import { env } from "../env";import { reportBackgroundError, runSentryCron } from "../lib/sentry";import { sendCheckerTasks } from "./checker";import { sendFollowUpEmails } from "./emails";import { handleExternalIncidentsPruneCron } from "./external-incidents-prune";import { handleExternalStatusCron } from "./external-status";import { LaunchMonitorWorkflow, Step3Days, Step14Days, StepPaused, workflowStepSchema,} from "./monitor";import { handleOAuthPruneCron } from "./oauth-prune";import { handleOutboxDrainCron, handleOutboxRetentionCron, handleOutboxShadowCron,} from "./outbox";import { handlePrivateLocationHealthCron } from "./private-location-health";import { handleStatusDriftCron } from "./status-drift";import { handleUptimeFreezeCron } from "./uptime-freeze";
const app = new Hono({ strict: false });
app.use("*", async (c, next) => { if (c.req.header("authorization") !== env().CRON_SECRET) { return c.text("Unauthorized", 401); }
return next();});
app.get("/checker/:period", async (c) => { const period = c.req.param("period");
const schema = monitorPeriodicitySchema.safeParse(period);
if (!schema.success) { return c.json({ error: schema.error.issues?.[0].message }, 400); } const periodicity = schema.data; const { cronCompleted, cronFailed } = runSentryCron(periodicity);
// Background chain: must not capture `c` or anything derived from it // (e.g. via getSentry(c)). The handler returns 200 before this resolves, and // a captured per-request Sentry hub stays pinned across retries โ see // apps/workflows/plan.md. void Effect.runPromise( Effect.tryPromise({ try: () => sendCheckerTasks(periodicity), // Surface `cause` โ DrizzleQueryError stringifies to "Failed query: โฆ" // without the underlying libSQL reason, so callers see no hint of *why* // the query failed unless we flatten the cause into the message. catch: (e) => { const causeMessage = e instanceof Error && e.cause instanceof Error ? `\nCause: ${e.cause.message}` : ""; return new Error( `Error in /checker/${periodicity} cron: ${e}${causeMessage}`, { cause: e }, ); }, }).pipe( Effect.retry({ times: 3, schedule: Schedule.exponential("1000 millis"), }), Effect.tap((result) => Effect.sync(() => { if (result.failed > 0) { void reportBackgroundError( `sendCheckerTasks for ${periodicity} ended with ${result.failed} failed tasks`, ); } void cronCompleted(); }), ), Effect.catch((e) => Effect.sync(() => { console.error(e); void reportBackgroundError(e.message); void cronFailed(); }), ), ), ); return c.json({ success: periodicity }, 200);});
app.get("/external-status", async (c) => { return handleExternalStatusCron(c);});
app.get("/external-incidents-prune", async (c) => { return handleExternalIncidentsPruneCron(c);});
app.get("/oauth-prune", async (c) => { return handleOAuthPruneCron(c);});
app.get("/uptime-freeze", async (c) => { return handleUptimeFreezeCron(c);});
app.get("/private-location-health", async (c) => { return handlePrivateLocationHealthCron(c);});
app.get("/emails/follow-up", async (c) => { const { cronCompleted, cronFailed } = runSentryCron("emails-follow-up");
try { await sendFollowUpEmails(); void cronCompleted(); return c.json({ success: true }, 200); } catch (e) { const errorName = e instanceof Error ? e.name : typeof e; void reportBackgroundError(`emails-follow-up failed: ${errorName}`); void cronFailed(); return c.text("Internal Server Error", 500); }});
app.get("/monitors", async (c) => { await LaunchMonitorWorkflow(); return c.json({ success: true }, 200);});
app.get("/monitors/:step", async (c) => { const step = c.req.param("step"); const schema = workflowStepSchema.safeParse(step);
const userId = c.req.query("userId"); const initialRun = c.req.query("initialRun"); if (!schema.success) { return c.json({ error: schema.error.issues?.[0].message }, 400); }
if (!userId) { Sentry.captureMessage("userId is missing in /monitors/:step cron", "error"); return c.json({ error: "userId is required" }, 400); } if (!initialRun) { Sentry.captureMessage( "initalRun is missing in /monitors/:step cron", "error", ); return c.json({ error: "initialRun is required" }, 400); }
switch (schema.data) { case "14days": await Step14Days(Number(userId), Number(initialRun)); break; case "3days": await Step3Days(Number(userId), Number(initialRun)); break; case "paused": await StepPaused(Number(userId), Number(initialRun)); break; default: throw new Error("Invalid step"); }
return c.json({ success: true }, 200);});
app.get("/outbox/drain", async (c) => { const summary = await handleOutboxDrainCron(); return c.json({ success: true, ...summary }, 200);});
app.get("/outbox/retention", async (c) => { const summary = await handleOutboxRetentionCron(); return c.json({ success: true, ...summary }, 200);});
app.get("/outbox/shadow", async (c) => { const result = await handleOutboxShadowCron(); return c.json({ success: true, ...result }, 200);});
app.get("/status-drift", async (c) => { const result = await handleStatusDriftCron(); return c.json({ success: true, ...result }, 200);});
export { app as cronRouter };