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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108import { and, eq } from "@openstatus/db";import { type Incident, type IncidentEventType, type IncidentStatus, incident,} from "@openstatus/db/src/schema";
import { emitAudit } from "../audit";import { requireScope } from "../auth";import { type ServiceContext, tryGetActorUserId, withTransaction,} from "../context";import { ConflictError } from "../errors";import { appendIncidentEvent, assertTransition, getIncidentInWorkspace,} from "./internal";import { SetIncidentStatusInput } from "./schemas";
function eventFor( from: IncidentStatus, to: IncidentStatus, note: string | undefined,): { type: IncidentEventType; message: string } { if (to === "resolved") { return { type: "resolved", message: note ?? "Incident resolved" }; } if (to === "canceled") { return { type: "canceled", message: note ?? "Incident canceled" }; } const summary = `Status changed from ${from} to ${to}`; return { type: "status_changed", message: note ? `${summary}\n\n${note}` : summary, };}
/** * Moves the incident along the transition table. The update is conditional on * the status read, so of two concurrent calls only one lands. */export async function setIncidentStatus(args: { ctx: ServiceContext; input: SetIncidentStatusInput;}): Promise<Incident> { const { ctx } = args; requireScope(ctx, "write"); const input = SetIncidentStatusInput.parse(args.input);
return withTransaction(ctx, async (tx) => { const existing = await getIncidentInWorkspace( tx, ctx.workspace.id, input.id, ); assertTransition(existing, input.status);
const now = new Date(); const patch: Partial<typeof incident.$inferInsert> = { status: input.status, updatedAt: now, }; if (input.status === "mitigated") { patch.mitigatedAt = existing.mitigatedAt ?? now; } if (input.status === "resolved") { patch.resolvedAt = now; patch.resolvedBy = tryGetActorUserId(ctx.actor); } if (input.status === "open") { patch.resolvedBy = null; } if (input.status === "canceled") { patch.closedAt = now; }
const updated = await tx .update(incident) .set(patch) .where( and(eq(incident.id, existing.id), eq(incident.status, existing.status)), ) .returning() .get(); if (!updated) { throw new ConflictError(`Incident #${existing.id} changed concurrently`); }
await emitAudit(tx, ctx, { action: "incident.update", entityType: "incident", entityId: updated.id, before: existing, after: updated, }); await appendIncidentEvent(tx, ctx, { incidentId: updated.id, ...eventFor(existing.status, input.status, input.note), createdAt: now, }); return updated; });}