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.
8.3 kB ยท 246 lines
TypeScript
at incident-api
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247import { and, eq, inArray, isNull } from "@openstatus/db";import { page, pageComponent, pageSubscriber, pageSubscriberToPageComponent, selectPageSubscriberSchema,} from "@openstatus/db/src/schema";
import { emitAudit } from "../audit";import { type DB, type ServiceContext, getReadDb, withTransaction,} from "../context";import { NotFoundError, ValidationError } from "../errors";import { type PageVisitor, assertPageAccess } from "../page-access";import { assertSubscribersAllowed, parseWorkspaceForContext } from "./internal";import { UpsertSelfSignupSubscriberInput } from "./schemas";
const VERIFICATION_EXPIRY_MS = 7 * 24 * 60 * 60 * 1000;
export type UpsertSelfSignupResult = { id: number; pageId: number; pageName: string; pageSlug: string; customDomain: string | null; channelType: "email" | "webhook" | "slack"; email: string; token: string | null; acceptedAt: Date | null; unsubscribedAt?: Date | null; componentIds: number[];};
/** * Self-signup entry point: a visitor on a public status page subscribes * by email. There is no authenticated actor and no `ctx.workspace` at * the call site โ both are resolved from the page row inside this * function so audit emission still happens with a `subscriber` actor * attributable to the resulting `pageSubscriber.id`. * * Audit emission rules: * - new row inserted โ `page_subscriber.create` * - pending row, scope merged โ `page_subscriber.update` with * `metadata: { componentIds: merged }` * (otherwise diffTopLevel would skip a * components-only change) * - already verified row โ no audit (no-op return) */// Token-addressed self-service by an anonymous visitor: the audit actor is// `subscriber`, for which `requireScope` is a documented no-op.// oxlint-disable-next-line openstatus/services-mutation-guardsexport async function upsertSelfSignupSubscriber(args: { input: UpsertSelfSignupSubscriberInput; /** `null` only when the caller already authorized the request (workspace API). */ visitor: PageVisitor | null; db?: DB;}): Promise<UpsertSelfSignupResult> { const input = UpsertSelfSignupSubscriberInput.parse(args.input); const componentIds = input.componentIds ?? [];
const readDb = getReadDb({ db: args.db } as ServiceContext); const pageData = await readDb.query.page.findFirst({ where: eq(page.id, input.pageId), with: { workspace: true }, }); if (!pageData) { throw new NotFoundError("page", input.pageId); } if (args.visitor) assertPageAccess(pageData, args.visitor); const workspace = parseWorkspaceForContext(pageData.workspace); // Plan-gate before any DB writes โ same upsell semantics as the // dashboard `createPageSubscriber` path. Free-plan pages don't // accept self-signups; the row never exists. assertSubscribersAllowed(workspace);
const emailLower = input.email.toLowerCase();
return withTransaction({ db: args.db } as ServiceContext, async (tx) => { if (componentIds.length > 0) { const valid = await tx .select({ id: pageComponent.id }) .from(pageComponent) .where( and( eq(pageComponent.pageId, input.pageId), inArray(pageComponent.id, componentIds), ), ) .all(); if (valid.length !== componentIds.length) { throw new ValidationError("Some components do not belong to this page"); } }
const existing = await tx.query.pageSubscriber.findFirst({ where: and( eq(pageSubscriber.email, emailLower), eq(pageSubscriber.pageId, input.pageId), eq(pageSubscriber.channelType, "email"), isNull(pageSubscriber.unsubscribedAt), ), with: { components: true }, });
if (existing?.acceptedAt) { // Already verified โ return as-is. Caller decides whether to surface // "already subscribed" to the visitor; no durable change here, no audit. return { id: existing.id, pageId: existing.pageId, pageName: pageData.title, pageSlug: pageData.slug, customDomain: pageData.customDomain, channelType: existing.channelType, email: existing.email ?? emailLower, token: existing.token, acceptedAt: existing.acceptedAt, componentIds: existing.components.map((c) => c.pageComponentId), }; }
if (existing) { // Pending row โ merge components, refresh expiry. const currentIds = existing.components.map((c) => c.pageComponentId); const mergedIds = [...new Set([...currentIds, ...componentIds])]; const newIds = mergedIds.filter((id) => !currentIds.includes(id));
if (newIds.length > 0) { await tx .insert(pageSubscriberToPageComponent) .values( newIds.map((compId) => ({ pageSubscriberId: existing.id, pageComponentId: compId, })), ) .onConflictDoNothing() .run(); }
const newExpiresAt = new Date(Date.now() + VERIFICATION_EXPIRY_MS); const beforeRow = selectPageSubscriberSchema.parse(existing); const updatedRow = await tx .update(pageSubscriber) .set({ expiresAt: newExpiresAt, updatedAt: new Date() }) .where(eq(pageSubscriber.id, existing.id)) .returning() .get(); const afterRow = selectPageSubscriberSchema.parse(updatedRow ?? existing);
const auditCtx: ServiceContext = { workspace, actor: { type: "subscriber", subscriberId: existing.id }, db: tx, };
// Strip token (capability for self-manage / unsubscribe URLs). // Component scope changes don't show in the row diff โ surface // them via metadata so the audit row carries actual signal. const { token: _bt, ...beforeSnap } = beforeRow; const { token: _at, ...afterSnap } = afterRow; await emitAudit(tx, auditCtx, { action: "page_subscriber.update", entityType: "page_subscriber", entityId: existing.id, before: beforeSnap, after: afterSnap, ...(newIds.length > 0 ? { metadata: { componentIds: mergedIds } } : {}), });
return { id: existing.id, pageId: existing.pageId, pageName: pageData.title, pageSlug: pageData.slug, customDomain: pageData.customDomain, channelType: existing.channelType, email: existing.email ?? emailLower, token: existing.token, acceptedAt: null, componentIds: mergedIds, }; }
// No existing row โ create. const token = crypto.randomUUID(); const expiresAt = new Date(Date.now() + VERIFICATION_EXPIRY_MS); const inserted = await tx .insert(pageSubscriber) .values({ channelType: "email", email: emailLower, webhookUrl: null, pageId: input.pageId, source: "self_signup", token, expiresAt, }) .returning() .get();
if (componentIds.length > 0) { await tx .insert(pageSubscriberToPageComponent) .values( componentIds.map((compId) => ({ pageSubscriberId: inserted.id, pageComponentId: compId, })), ) .run(); }
const auditCtx: ServiceContext = { workspace, actor: { type: "subscriber", subscriberId: inserted.id }, db: tx, }; const { token: _t, ...after } = selectPageSubscriberSchema.parse(inserted); await emitAudit(tx, auditCtx, { action: "page_subscriber.create", entityType: "page_subscriber", entityId: inserted.id, after, ...(componentIds.length > 0 ? { metadata: { componentIds } } : {}), });
return { id: inserted.id, pageId: inserted.pageId, pageName: pageData.title, pageSlug: pageData.slug, customDomain: pageData.customDomain, channelType: "email" as const, email: inserted.email ?? emailLower, token: inserted.token, acceptedAt: inserted.acceptedAt ?? null, unsubscribedAt: inserted.unsubscribedAt ?? null, componentIds, }; });}