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.7 kB ยท 185 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186import { page, pageComponent, selectPageSchema,} from "@openstatus/db/src/schema";
import { emitAudit } from "../audit";import { requireScope } from "../auth";import { type ServiceContext, withTransaction } from "../context";import { LimitExceededError } from "../errors";import { assertWithinLimit } from "../limits";import type { Page } from "../types";import { assertAccessTypeAllowed, assertSlugAvailable, validateMonitorIdsActive,} from "./internal";import { CreatePageInput, NewPageInput } from "./schemas";
/** Full create โ mirrors legacy `pageRouter.create` (insertPageSchema input). */export async function createPage(args: { ctx: ServiceContext; input: CreatePageInput;}): Promise<Page> { const { ctx } = args; requireScope(ctx, "write"); const input = CreatePageInput.parse(args.input);
if (input.customTheme && !ctx.workspace.limits["custom-theme"]) { throw new LimitExceededError("custom-theme", 0); } if ( !ctx.workspace.limits.i18n && (input.defaultLocale !== "en" || input.locales?.some((locale) => locale !== "en")) ) { throw new LimitExceededError("i18n", 0); }
return withTransaction(ctx, async (tx) => { await assertWithinLimit({ tx, workspaceId: ctx.workspace.id, limit: "status-pages", }); await assertSlugAvailable({ tx, slug: input.slug }); assertAccessTypeAllowed(ctx.workspace, { accessType: input.accessType ?? "public", passwordProtected: input.passwordProtected ?? null, allowedIpRanges: input.allowedIpRanges ?? null, allowIndex: input.allowIndex, });
const { monitors, workspaceId: _ws, id: _id, configuration, ...pageProps } = input; const monitorIds = monitors?.map((m) => m.monitorId) ?? [];
const row = await tx .insert(page) .values({ workspaceId: ctx.workspace.id, // `page.configuration` is a drizzle `text("โฆ", { mode: "json" })` // column โ drizzle serialises objects automatically. Calling // `JSON.stringify` first would double-encode and persist a raw // JSON string, breaking downstream reads that expect an object. configuration, ...pageProps, authEmailDomains: pageProps.authEmailDomains?.join(","), allowedIpRanges: pageProps.allowedIpRanges?.join(","), }) .returning() .get();
if (monitorIds.length > 0) { const validMonitors = await validateMonitorIdsActive({ tx, workspaceId: ctx.workspace.id, monitorIds, }); const monitorMap = new Map(validMonitors.map((m) => [m.id, m])); const pageComponentValues = (monitors ?? []) .map(({ monitorId }, index) => { const m = monitorMap.get(monitorId); if (!m || !m.workspaceId) return null; return { workspaceId: m.workspaceId, pageId: row.id, type: "monitor" as const, monitorId, name: m.externalName || m.name, order: index, groupId: null, groupOrder: 0, }; }) .filter((v): v is NonNullable<typeof v> => v !== null); if (pageComponentValues.length > 0) { await assertWithinLimit({ tx, workspaceId: ctx.workspace.id, limit: "page-components", delta: pageComponentValues.length, }); await tx.insert(pageComponent).values(pageComponentValues).run(); } }
await emitAudit(tx, ctx, { action: "page.create", entityType: "page", entityId: row.id, after: row, metadata: { slug: row.slug }, });
// `selectPageSchema.parse` normalises the drizzle row into the // `Page` shape callers expect: `authEmailDomains` / `allowedIpRanges` // go from the raw comma-joined string (drizzle-inferred) to the // `string[]` the `selectPageSchema` defines. Previously this used // `row as unknown as Page`, which hid the drift. return selectPageSchema.parse(row); });}
/** Minimal create โ matches the dashboard onboarding `new` shape. */export async function newPage(args: { ctx: ServiceContext; input: NewPageInput;}): Promise<Page> { const { ctx } = args; requireScope(ctx, "write"); const input = NewPageInput.parse(args.input);
return withTransaction(ctx, async (tx) => { await assertWithinLimit({ tx, workspaceId: ctx.workspace.id, limit: "status-pages", }); await assertSlugAvailable({ tx, slug: input.slug });
const defaultConfiguration = { type: "absolute", value: "requests", uptime: true, theme: "default-rounded", };
const row = await tx .insert(page) .values({ workspaceId: ctx.workspace.id, title: input.title, slug: input.slug, description: input.description ?? "", icon: input.icon ?? "", legacyPage: false, configuration: defaultConfiguration, customDomain: "", allowIndex: true, }) .returning() .get();
await emitAudit(tx, ctx, { action: "page.create", entityType: "page", entityId: row.id, after: row, metadata: { slug: row.slug, source: "new" }, });
// `selectPageSchema.parse` normalises the drizzle row into the // `Page` shape callers expect: `authEmailDomains` / `allowedIpRanges` // go from the raw comma-joined string (drizzle-inferred) to the // `string[]` the `selectPageSchema` defines. Previously this used // `row as unknown as Page`, which hid the drift. return selectPageSchema.parse(row); });}