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.
4.0 kB ยท 120 lines
TypeScript
at incident-api
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121import { and, eq, inArray } from "@openstatus/db";import { pageComponent, pageSubscriber, pageSubscriberToPageComponent, selectPageSubscriberSchema,} from "@openstatus/db/src/schema";
import { emitAudit } from "../audit";import { type DB, type ServiceContext, withTransaction } from "../context";import { NotFoundError, ValidationError } from "../errors";import { resolveSubscriberByToken } from "./internal";import { UpdateSubscriberScopeInput } from "./schemas";
/** * Replace a self-signup subscriber's component scope (the set of * components they receive notifications for). Token-addressed; the * subscriber acts on themselves, so the audit row is attributed to * `subscriber:{id}`. * * Component-only changes don't show up in `selectPageSubscriberSchema` * โ `componentIds` is on the join table, not the row โ so we surface * them via `metadata` to keep the audit row from being dropped by the * empty-diff guard in `emitAudit`. */// 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 updateSubscriberScope(args: { input: UpdateSubscriberScopeInput; db?: DB;}): Promise<void> { const input = UpdateSubscriberScopeInput.parse(args.input);
const resolved = await resolveSubscriberByToken({ db: args.db, token: input.token, domain: input.domain, }); if (!resolved) throw new NotFoundError("page_subscriber"); const { row, pageData, components: currentIds } = resolved;
if (!row.acceptedAt) { throw new ValidationError("Subscription not yet verified"); } if (row.unsubscribedAt) { throw new ValidationError("Subscription is unsubscribed"); }
await withTransaction({ db: args.db } as ServiceContext, async (tx) => { if (input.componentIds.length > 0) { const valid = await tx .select({ id: pageComponent.id }) .from(pageComponent) .where( and( eq(pageComponent.pageId, row.pageId), inArray(pageComponent.id, input.componentIds), ), ) .all(); if (valid.length !== input.componentIds.length) { throw new ValidationError("Some components do not belong to this page"); } }
await tx .delete(pageSubscriberToPageComponent) .where(eq(pageSubscriberToPageComponent.pageSubscriberId, row.id)) .run();
if (input.componentIds.length > 0) { await tx .insert(pageSubscriberToPageComponent) .values( input.componentIds.map((id) => ({ pageSubscriberId: row.id, pageComponentId: id, })), ) .run(); }
const before = selectPageSubscriberSchema.parse(row); const updated = await tx .update(pageSubscriber) .set({ updatedAt: new Date() }) .where(eq(pageSubscriber.id, row.id)) .returning() .get(); const after = selectPageSubscriberSchema.parse(updated ?? row);
const auditCtx: ServiceContext = { workspace: pageData.workspace, actor: { type: "subscriber", subscriberId: row.id }, db: tx, }; const { token: _bt, ...beforeSnap } = before; const { token: _at, ...afterSnap } = after;
// Sort defensively โ the diff is order-sensitive for arrays, but the // semantic identity of the scope is set-equality, not list order. const previous = [...currentIds].sort((a, b) => a - b); const next = [...input.componentIds].sort((a, b) => a - b); const changed = previous.length !== next.length || previous.some((id, i) => id !== next[i]);
await emitAudit(tx, auditCtx, { action: "page_subscriber.update", entityType: "page_subscriber", entityId: row.id, before: beforeSnap, after: afterSnap, ...(changed ? { metadata: { previousComponentIds: previous, componentIds: next } } : {}), }); });}