From 79ec38f7f7f1f9005fd242158ba9277cdb38d88f Mon Sep 17 00:00:00 2001 From: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com> Date: Thu, 1 Oct 2026 18:07:26 +0200 Subject: [PATCH] chore: created updated by status-report maintenance (#2829) * chore: created updated by status-report maintenance * fix: tracker * fix: address review on attribution columns - incident user projections carry deletedAt so soft-deleted users read as "Deleted user" - deleting a status report update always stamps updated_by on the report (new `touch` option) - enrichment doc comment counts three round-trips - tighten attribution tests: update-row assertions, phase status guard, cleanup in finally * fix: ci on attribution columns - api mock fixtures carry created_by/updated_by - force the report write only when the deleted update was the latest; non-latest deletions keep updatedAt untouched for the feed * fix: drop displayName re-export and person aliases * fix: keep displayName on the incident package entry api and server import it from @openstatus/services/incident; re-export from attribution instead of internal --- .../v1/maintenances/attribution.test.ts | 69 + .../server/src/routes/v1/maintenances/post.ts | 3 + apps/server/src/routes/v1/maintenances/put.ts | 2 + .../src/routes/v1/statusReportUpdates/post.ts | 4 + .../v1/statusReports/attribution.test.ts | 84 + .../src/routes/v1/statusReports/post.ts | 5 + .../routes/v1/statusReports/update/post.ts | 9 +- .../api/src/router/statusPage.utils.test.ts | 10 + packages/db/drizzle/0091_amused_wrecker.sql | 6 + packages/db/drizzle/meta/0091_snapshot.json | 6776 +++++++++++++++++ packages/db/drizzle/meta/_journal.json | 7 + .../db/src/schema/maintenances/maintenance.ts | 14 + packages/db/src/schema/shared.ts | 69 +- .../schema/status_reports/status_reports.ts | 28 + packages/services/src/agent-tools/incident.ts | 30 +- .../services/src/agent-tools/maintenance.ts | 5 + .../services/src/agent-tools/status-report.ts | 7 + packages/services/src/attribution.ts | 73 + .../src/import/__tests__/attribution.test.ts | 136 + packages/services/src/import/phase-writers.ts | 10 +- packages/services/src/incident/index.ts | 3 +- packages/services/src/incident/internal.ts | 11 +- packages/services/src/incident/list-events.ts | 1 + packages/services/src/incident/list.ts | 1 + .../services/src/incident/postmortem-draft.ts | 2 +- .../maintenance/__tests__/attribution.test.ts | 120 + packages/services/src/maintenance/create.ts | 9 +- packages/services/src/maintenance/list.ts | 12 + packages/services/src/maintenance/update.ts | 11 +- .../__tests__/attribution.test.ts | 252 + .../services/src/status-report/add-update.ts | 10 +- packages/services/src/status-report/create.ts | 11 +- packages/services/src/status-report/delete.ts | 11 +- .../src/status-report/derive-status.ts | 20 +- packages/services/src/status-report/list.ts | 57 +- packages/services/src/status-report/update.ts | 22 +- .../status-timeline/__tests__/events.test.ts | 2 + packages/tracker/src/tracker.test.ts | 6 + packages/tracker/src/tracker.ts | 10 +- 39 files changed, 7817 insertions(+), 101 deletions(-) create mode 100644 apps/server/src/routes/v1/maintenances/attribution.test.ts create mode 100644 apps/server/src/routes/v1/statusReports/attribution.test.ts create mode 100644 packages/db/drizzle/0091_amused_wrecker.sql create mode 100644 packages/db/drizzle/meta/0091_snapshot.json create mode 100644 packages/services/src/attribution.ts create mode 100644 packages/services/src/import/__tests__/attribution.test.ts create mode 100644 packages/services/src/maintenance/__tests__/attribution.test.ts create mode 100644 packages/services/src/status-report/__tests__/attribution.test.ts diff --git a/apps/server/src/routes/v1/maintenances/attribution.test.ts b/apps/server/src/routes/v1/maintenances/attribution.test.ts new file mode 100644 index 000000000..f7e55962c --- /dev/null +++ b/apps/server/src/routes/v1/maintenances/attribution.test.ts @@ -0,0 +1,69 @@ +import { OpenAPIHono } from "@hono/zod-openapi"; +import { db, eq } from "@openstatus/db"; +import { maintenance } from "@openstatus/db/src/schema"; +import { expect } from "@std/expect"; +import { test } from "@std/testing/bdd"; + +import { lookupWorkspace } from "@/libs/middlewares/auth"; + +import type { Variables } from "../index"; +import { maintenancesApi } from "./index"; + +/** The v1 router with `authMiddleware` replaced by a fixed key identity. */ +function makeApp(createdById: number | undefined) { + const app = new OpenAPIHono<{ Variables: Variables }>(); + app.use("*", async (c, next) => { + c.set("workspace", await lookupWorkspace(1)); + c.set("apiKey", { id: "test-key", createdById, scopes: ["write"] }); + await next(); + }); + app.route("/", maintenancesApi); + return app; +} + +test("v1 maintenance stamps the key's creator on create and update", async () => { + const from = new Date(); + const to = new Date(from.getTime() + 3600000); + const created = await makeApp(1).request("/", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + title: "v1-attribution", + message: "m", + from: from.toISOString(), + to: to.toISOString(), + pageId: 1, + }), + }); + let id: number | undefined; + try { + expect(created.status).toBe(200); + id = ((await created.json()) as { id: number }).id; + let row = await db + .select() + .from(maintenance) + .where(eq(maintenance.id, id)) + .get(); + expect(row?.createdBy).toBe(1); + expect(row?.updatedBy).toBe(1); + + // a second key without a creator edits it: updated_by clears, created_by stays + const updated = await makeApp(undefined).request(`/${id}`, { + method: "PUT", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ title: "v1-attribution-renamed" }), + }); + expect(updated.status).toBe(200); + row = await db + .select() + .from(maintenance) + .where(eq(maintenance.id, id)) + .get(); + expect(row?.createdBy).toBe(1); + expect(row?.updatedBy).toBeNull(); + } finally { + if (id !== undefined) { + await db.delete(maintenance).where(eq(maintenance.id, id)); + } + } +}); diff --git a/apps/server/src/routes/v1/maintenances/post.ts b/apps/server/src/routes/v1/maintenances/post.ts index 78ae6676b..79abf3f98 100644 --- a/apps/server/src/routes/v1/maintenances/post.ts +++ b/apps/server/src/routes/v1/maintenances/post.ts @@ -54,6 +54,7 @@ const postRoute = createRoute({ export function registerPostMaintenance(api: typeof maintenancesApi) { return api.openapi(postRoute, async (c) => { const workspaceId = c.get("workspace").id; + const actorUserId = c.get("apiKey").createdById ?? null; const input = c.req.valid("json"); const limits = c.get("workspace").limits; @@ -104,6 +105,8 @@ export function registerPostMaintenance(api: typeof maintenancesApi) { .values({ ...input, workspaceId, + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning() .get(); diff --git a/apps/server/src/routes/v1/maintenances/put.ts b/apps/server/src/routes/v1/maintenances/put.ts index 2e67a4cf3..c36e303e7 100644 --- a/apps/server/src/routes/v1/maintenances/put.ts +++ b/apps/server/src/routes/v1/maintenances/put.ts @@ -54,6 +54,7 @@ const putRoute = createRoute({ export function registerPutMaintenance(api: typeof maintenancesApi) { return api.openapi(putRoute, async (c) => { const workspaceId = c.get("workspace").id; + const actorUserId = c.get("apiKey").createdById ?? null; const { id } = c.req.valid("param"); const input = c.req.valid("json"); @@ -132,6 +133,7 @@ export function registerPutMaintenance(api: typeof maintenancesApi) { .set({ ...input, updatedAt: new Date(), + updatedBy: actorUserId, }) .where(eq(maintenance.id, Number(id))) .returning() diff --git a/apps/server/src/routes/v1/statusReportUpdates/post.ts b/apps/server/src/routes/v1/statusReportUpdates/post.ts index c90552282..deb8c256f 100644 --- a/apps/server/src/routes/v1/statusReportUpdates/post.ts +++ b/apps/server/src/routes/v1/statusReportUpdates/post.ts @@ -41,6 +41,7 @@ export function registerPostStatusReportUpdate( ) { return api.openapi(createStatusUpdate, async (c) => { const workspaceId = c.get("workspace").id; + const actorUserId = c.get("apiKey").createdById ?? null; const input = c.req.valid("json"); const limits = c.get("workspace").limits; @@ -72,6 +73,8 @@ export function registerPostStatusReportUpdate( ...input, date: new Date(input.date), statusReportId: _statusReport.id, + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning() .get(); @@ -81,6 +84,7 @@ export function registerPostStatusReportUpdate( .set({ status: input.status, updatedAt: new Date(), + updatedBy: actorUserId, }) .where(eq(statusReport.id, _statusReport.id)); diff --git a/apps/server/src/routes/v1/statusReports/attribution.test.ts b/apps/server/src/routes/v1/statusReports/attribution.test.ts new file mode 100644 index 000000000..99f97c113 --- /dev/null +++ b/apps/server/src/routes/v1/statusReports/attribution.test.ts @@ -0,0 +1,84 @@ +import { OpenAPIHono } from "@hono/zod-openapi"; +import { db, eq } from "@openstatus/db"; +import { statusReport, statusReportUpdate } from "@openstatus/db/src/schema"; +import { expect } from "@std/expect"; +import { test } from "@std/testing/bdd"; + +import { lookupWorkspace } from "@/libs/middlewares/auth"; + +import type { Variables } from "../index"; +import { statusReportsApi } from "./index"; + +/** The v1 router with `authMiddleware` replaced by a fixed key identity. */ +function makeApp(createdById: number | undefined) { + const app = new OpenAPIHono<{ Variables: Variables }>(); + app.use("*", async (c, next) => { + c.set("workspace", await lookupWorkspace(1)); + c.set("apiKey", { id: "test-key", createdById, scopes: ["write"] }); + await next(); + }); + app.route("/", statusReportsApi); + return app; +} + +async function post(createdById: number | undefined, title: string) { + const res = await makeApp(createdById).request("/", { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify({ + status: "investigating", + title, + message: "m", + monitorIds: [], + date: new Date().toISOString(), + pageId: 1, + }), + }); + expect(res.status).toBe(200); + const body = (await res.json()) as { id: number }; + return body.id; +} + +test("v1 status report stamps the key's creator", async () => { + const id = await post(1, "v1-attribution-user"); + try { + const report = await db + .select() + .from(statusReport) + .where(eq(statusReport.id, id)) + .get(); + expect(report?.createdBy).toBe(1); + expect(report?.updatedBy).toBe(1); + const updates = await db + .select() + .from(statusReportUpdate) + .where(eq(statusReportUpdate.statusReportId, id)) + .all(); + expect(updates.map((u) => [u.createdBy, u.updatedBy])).toEqual([[1, 1]]); + } finally { + await db.delete(statusReport).where(eq(statusReport.id, id)); + } +}); + +test("v1 status report leaves NULL for a key without a creator", async () => { + const id = await post(undefined, "v1-attribution-anon"); + try { + const report = await db + .select() + .from(statusReport) + .where(eq(statusReport.id, id)) + .get(); + expect(report?.createdBy).toBeNull(); + expect(report?.updatedBy).toBeNull(); + const updates = await db + .select() + .from(statusReportUpdate) + .where(eq(statusReportUpdate.statusReportId, id)) + .all(); + expect(updates.map((u) => [u.createdBy, u.updatedBy])).toEqual([ + [null, null], + ]); + } finally { + await db.delete(statusReport).where(eq(statusReport.id, id)); + } +}); diff --git a/apps/server/src/routes/v1/statusReports/post.ts b/apps/server/src/routes/v1/statusReports/post.ts index 397b43b41..e6ba9ff10 100644 --- a/apps/server/src/routes/v1/statusReports/post.ts +++ b/apps/server/src/routes/v1/statusReports/post.ts @@ -58,6 +58,7 @@ export function registerPostStatusReport(api: typeof statusReportsApi) { return api.openapi(postRoute, async (c) => { const input = c.req.valid("json"); const workspaceId = c.get("workspace").id; + const actorUserId = c.get("apiKey").createdById ?? null; const limits = c.get("workspace").limits; if (input.monitorIds?.length) { @@ -105,6 +106,8 @@ export function registerPostStatusReport(api: typeof statusReportsApi) { title: input.title, pageId: input.pageId, workspaceId: workspaceId, + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning() .get(); @@ -116,6 +119,8 @@ export function registerPostStatusReport(api: typeof statusReportsApi) { message: input.message, date: input.date, statusReportId: _newStatusReport.id, + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning() .get(); diff --git a/apps/server/src/routes/v1/statusReports/update/post.ts b/apps/server/src/routes/v1/statusReports/update/post.ts index 6f899172e..0cf28d27f 100644 --- a/apps/server/src/routes/v1/statusReports/update/post.ts +++ b/apps/server/src/routes/v1/statusReports/update/post.ts @@ -57,11 +57,16 @@ export function registerStatusReportUpdateRoutes(api: typeof statusReportsApi) { const input = c.req.valid("json"); const { id } = c.req.valid("param"); const workspaceId = c.get("workspace").id; + const actorUserId = c.get("apiKey").createdById ?? null; const limits = c.get("workspace").limits; const _statusReport = await db .update(statusReport) - .set({ status: input.status, updatedAt: new Date() }) + .set({ + status: input.status, + updatedAt: new Date(), + updatedBy: actorUserId, + }) .where( and( eq(statusReport.id, Number(id)), @@ -85,6 +90,8 @@ export function registerStatusReportUpdateRoutes(api: typeof statusReportsApi) { message: input.message, date: input.date, statusReportId: Number(id), + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning() .get(); diff --git a/packages/api/src/router/statusPage.utils.test.ts b/packages/api/src/router/statusPage.utils.test.ts index f3cf8b330..30a7ff249 100644 --- a/packages/api/src/router/statusPage.utils.test.ts +++ b/packages/api/src/router/statusPage.utils.test.ts @@ -1081,6 +1081,8 @@ describe("getEvents - pageComponent filtering", () => { to, workspaceId: 1, pageId: 1, + createdBy: null, + updatedBy: null, createdAt: new Date(), updatedAt: new Date(), maintenancesToPageComponents: pageComponentIds.map((pcId) => ({ @@ -1109,6 +1111,8 @@ describe("getEvents - pageComponent filtering", () => { status, workspaceId: 1, pageId: 1, + createdBy: null, + updatedBy: null, createdAt: new Date(), updatedAt: new Date(), statusReportsToPageComponents: pageComponentIds.map((pcId) => ({ @@ -1121,6 +1125,8 @@ describe("getEvents - pageComponent filtering", () => { date: updateDate, status: "investigating", message: "Investigating the issue", + createdBy: null, + updatedBy: null, createdAt: new Date(), updatedAt: new Date(), }, @@ -1377,6 +1383,8 @@ describe("componentImpacts", () => { status: args.status ?? ("investigating" as const), workspaceId: 1, pageId: 1, + createdBy: null, + updatedBy: null, createdAt: new Date(), updatedAt: new Date(), statusReportsToPageComponents: args.components.map((pcId) => ({ @@ -1388,6 +1396,8 @@ describe("componentImpacts", () => { date: u.date, status: u.status, message: "m", + createdBy: null, + updatedBy: null, createdAt: new Date(), updatedAt: new Date(), statusReportUpdateToPageComponents: (u.impacts ?? []).map((r) => ({ diff --git a/packages/db/drizzle/0091_amused_wrecker.sql b/packages/db/drizzle/0091_amused_wrecker.sql new file mode 100644 index 000000000..3958bae03 --- /dev/null +++ b/packages/db/drizzle/0091_amused_wrecker.sql @@ -0,0 +1,6 @@ +ALTER TABLE `status_report` ADD `created_by` integer REFERENCES user(id);--> statement-breakpoint +ALTER TABLE `status_report` ADD `updated_by` integer REFERENCES user(id);--> statement-breakpoint +ALTER TABLE `status_report_update` ADD `created_by` integer REFERENCES user(id);--> statement-breakpoint +ALTER TABLE `status_report_update` ADD `updated_by` integer REFERENCES user(id);--> statement-breakpoint +ALTER TABLE `maintenance` ADD `created_by` integer REFERENCES user(id);--> statement-breakpoint +ALTER TABLE `maintenance` ADD `updated_by` integer REFERENCES user(id); \ No newline at end of file diff --git a/packages/db/drizzle/meta/0091_snapshot.json b/packages/db/drizzle/meta/0091_snapshot.json new file mode 100644 index 000000000..64f749fc3 --- /dev/null +++ b/packages/db/drizzle/meta/0091_snapshot.json @@ -0,0 +1,6776 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "2dde5587-d4ea-4734-973a-bb75b6a23940", + "prevId": "962ab282-b5aa-4e15-8252-1f04bfdf39f0", + "tables": { + "workspace": { + "name": "workspace", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "stripe_id": { + "name": "stripe_id", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subscription_id": { + "name": "subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ends_at": { + "name": "ends_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "paid_until": { + "name": "paid_until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "limits": { + "name": "limits", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{}'" + }, + "workos_organization_id": { + "name": "workos_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sso_enabled": { + "name": "sso_enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "dsn": { + "name": "dsn", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "workspace_slug_unique": { + "name": "workspace_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "workspace_stripe_id_unique": { + "name": "workspace_stripe_id_unique", + "columns": [ + "stripe_id" + ], + "isUnique": true + }, + "workspace_workos_organization_id_unique": { + "name": "workspace_workos_organization_id_unique", + "columns": [ + "workos_organization_id" + ], + "isUnique": true + }, + "workspace_id_dsn_unique": { + "name": "workspace_id_dsn_unique", + "columns": [ + "id", + "dsn" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspace_sso_domain": { + "name": "workspace_sso_domain", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "verified_at": { + "name": "verified_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "workspace_sso_domain_domain_unique": { + "name": "workspace_sso_domain_domain_unique", + "columns": [ + "domain" + ], + "isUnique": true + }, + "workspace_sso_domain_workspace_id_idx": { + "name": "workspace_sso_domain_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspace_sso_domain_workspace_id_workspace_id_fk": { + "name": "workspace_sso_domain_workspace_id_workspace_id_fk", + "tableFrom": "workspace_sso_domain", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_provider_account_id_pk": { + "columns": [ + "provider", + "provider_account_id" + ], + "name": "account_provider_provider_account_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_user_id_idx": { + "name": "session_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tenant_id": { + "name": "tenant_id", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "photo_url": { + "name": "photo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_tenant_id_unique": { + "name": "user_tenant_id_unique", + "columns": [ + "tenant_id" + ], + "isUnique": true + }, + "user_email_idx": { + "name": "user_email_idx", + "columns": [ + "email" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users_to_workspaces": { + "name": "users_to_workspaces", + "columns": { + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "users_to_workspaces_workspace_id_idx": { + "name": "users_to_workspaces_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "users_to_workspaces_user_id_user_id_fk": { + "name": "users_to_workspaces_user_id_user_id_fk", + "tableFrom": "users_to_workspaces", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "users_to_workspaces_workspace_id_workspace_id_fk": { + "name": "users_to_workspaces_workspace_id_workspace_id_fk", + "tableFrom": "users_to_workspaces", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "users_to_workspaces_user_id_workspace_id_pk": { + "columns": [ + "user_id", + "workspace_id" + ], + "name": "users_to_workspaces_user_id_workspace_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification_token": { + "name": "verification_token", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verification_token_identifier_token_pk": { + "columns": [ + "identifier", + "token" + ], + "name": "verification_token_identifier_token_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "status_report": { + "name": "status_report", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "status_report_workspace_created_idx": { + "name": "status_report_workspace_created_idx", + "columns": [ + "workspace_id", + "created_at" + ], + "isUnique": false + }, + "status_report_page_id_idx": { + "name": "status_report_page_id_idx", + "columns": [ + "page_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "status_report_workspace_id_workspace_id_fk": { + "name": "status_report_workspace_id_workspace_id_fk", + "tableFrom": "status_report", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_report_page_id_page_id_fk": { + "name": "status_report_page_id_page_id_fk", + "tableFrom": "status_report", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_report_created_by_user_id_fk": { + "name": "status_report_created_by_user_id_fk", + "tableFrom": "status_report", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_report_updated_by_user_id_fk": { + "name": "status_report_updated_by_user_id_fk", + "tableFrom": "status_report", + "tableTo": "user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "status_report_update": { + "name": "status_report_update", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "date": { + "name": "date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status_report_id": { + "name": "status_report_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "status_report_update_status_report_id_idx": { + "name": "status_report_update_status_report_id_idx", + "columns": [ + "status_report_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "status_report_update_status_report_id_status_report_id_fk": { + "name": "status_report_update_status_report_id_status_report_id_fk", + "tableFrom": "status_report_update", + "tableTo": "status_report", + "columnsFrom": [ + "status_report_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_report_update_created_by_user_id_fk": { + "name": "status_report_update_created_by_user_id_fk", + "tableFrom": "status_report_update", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "status_report_update_updated_by_user_id_fk": { + "name": "status_report_update_updated_by_user_id_fk", + "tableFrom": "status_report_update", + "tableTo": "user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "integration": { + "name": "integration", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "credential": { + "name": "credential", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "integration_workspace_id_idx": { + "name": "integration_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "integration_workspace_id_workspace_id_fk": { + "name": "integration_workspace_id_workspace_id_fk", + "tableFrom": "integration", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "page": { + "name": "page", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "slug": { + "name": "slug", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "custom_domain": { + "name": "custom_domain", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "published": { + "name": "published", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "force_theme": { + "name": "force_theme", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'system'" + }, + "custom_theme": { + "name": "custom_theme", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password_protected": { + "name": "password_protected", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "access_type": { + "name": "access_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'public'" + }, + "auth_email_domains": { + "name": "auth_email_domains", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allowed_ip_ranges": { + "name": "allowed_ip_ranges", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "homepage_url": { + "name": "homepage_url", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contact_url": { + "name": "contact_url", + "type": "text(256)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_locale": { + "name": "default_locale", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'en'" + }, + "locales": { + "name": "locales", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "legacy_page": { + "name": "legacy_page", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "configuration": { + "name": "configuration", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allow_index": { + "name": "allow_index", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "show_monitor_values": { + "name": "show_monitor_values", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "page_slug_unique": { + "name": "page_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "page_lower_slug_idx": { + "name": "page_lower_slug_idx", + "columns": [ + "LOWER(\"slug\")" + ], + "isUnique": false + }, + "page_lower_custom_domain_idx": { + "name": "page_lower_custom_domain_idx", + "columns": [ + "LOWER(\"custom_domain\")" + ], + "isUnique": false + }, + "page_workspace_id_idx": { + "name": "page_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "page_workspace_id_workspace_id_fk": { + "name": "page_workspace_id_workspace_id_fk", + "tableFrom": "page", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor": { + "name": "monitor", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "job_type": { + "name": "job_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'http'" + }, + "periodicity": { + "name": "periodicity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'other'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "active": { + "name": "active", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "regions": { + "name": "regions", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "url": { + "name": "url", + "type": "text(2048)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "external_name": { + "name": "external_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "headers": { + "name": "headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'GET'" + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 45000 + }, + "degraded_after": { + "name": "degraded_after", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assertions": { + "name": "assertions", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "otel_endpoint": { + "name": "otel_endpoint", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "otel_headers": { + "name": "otel_headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public": { + "name": "public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "retry": { + "name": "retry", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 3 + }, + "follow_redirects": { + "name": "follow_redirects", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "grpc_service": { + "name": "grpc_service", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "grpc_tls": { + "name": "grpc_tls", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'tls'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "monitor_workspace_id_active_idx": { + "name": "monitor_workspace_id_active_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false, + "where": "\"monitor\".\"deleted_at\" IS NULL" + } + }, + "foreignKeys": { + "monitor_workspace_id_workspace_id_fk": { + "name": "monitor_workspace_id_workspace_id_fk", + "tableFrom": "monitor", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "page_subscriber": { + "name": "page_subscriber", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_type": { + "name": "channel_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'email'" + }, + "webhook_url": { + "name": "webhook_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "channel_config": { + "name": "channel_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'self_signup'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "unsubscribed_at": { + "name": "unsubscribed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "page_subscriber_page_id_idx": { + "name": "page_subscriber_page_id_idx", + "columns": [ + "page_id" + ], + "isUnique": false + }, + "idx_page_subscriber_email_page_active": { + "name": "idx_page_subscriber_email_page_active", + "columns": [ + "LOWER(\"email\")", + "page_id" + ], + "isUnique": true, + "where": "\"page_subscriber\".\"unsubscribed_at\" IS NULL AND \"page_subscriber\".\"channel_type\" = 'email'" + }, + "idx_page_subscriber_webhook_page_active": { + "name": "idx_page_subscriber_webhook_page_active", + "columns": [ + "LOWER(\"webhook_url\")", + "page_id" + ], + "isUnique": true, + "where": "\"page_subscriber\".\"unsubscribed_at\" IS NULL AND \"page_subscriber\".\"channel_type\" = 'webhook'" + }, + "idx_page_subscriber_slack_channel_page_active": { + "name": "idx_page_subscriber_slack_channel_page_active", + "columns": [ + "slack_channel_id", + "page_id" + ], + "isUnique": true, + "where": "\"page_subscriber\".\"unsubscribed_at\" IS NULL AND \"page_subscriber\".\"channel_type\" = 'slack'" + } + }, + "foreignKeys": { + "page_subscriber_page_id_page_id_fk": { + "name": "page_subscriber_page_id_page_id_fk", + "tableFrom": "page_subscriber", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "page_subscriber_channel_check": { + "name": "page_subscriber_channel_check", + "value": "(\"page_subscriber\".\"channel_type\" = 'email' AND \"page_subscriber\".\"email\" IS NOT NULL AND \"page_subscriber\".\"webhook_url\" IS NULL) OR (\"page_subscriber\".\"channel_type\" = 'webhook' AND \"page_subscriber\".\"webhook_url\" IS NOT NULL AND \"page_subscriber\".\"email\" IS NULL) OR (\"page_subscriber\".\"channel_type\" = 'slack' AND \"page_subscriber\".\"slack_channel_id\" IS NOT NULL AND \"page_subscriber\".\"email\" IS NULL AND \"page_subscriber\".\"webhook_url\" IS NULL)" + } + } + }, + "page_subscriber_to_page_component": { + "name": "page_subscriber_to_page_component", + "columns": { + "page_subscriber_id": { + "name": "page_subscriber_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_component_id": { + "name": "page_component_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": {}, + "foreignKeys": { + "page_subscriber_to_page_component_page_subscriber_id_page_subscriber_id_fk": { + "name": "page_subscriber_to_page_component_page_subscriber_id_page_subscriber_id_fk", + "tableFrom": "page_subscriber_to_page_component", + "tableTo": "page_subscriber", + "columnsFrom": [ + "page_subscriber_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "page_subscriber_to_page_component_page_component_id_page_component_id_fk": { + "name": "page_subscriber_to_page_component_page_component_id_page_component_id_fk", + "tableFrom": "page_subscriber_to_page_component", + "tableTo": "page_component", + "columnsFrom": [ + "page_component_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "page_subscriber_to_page_component_page_subscriber_id_page_component_id_pk": { + "columns": [ + "page_subscriber_id", + "page_component_id" + ], + "name": "page_subscriber_to_page_component_page_subscriber_id_page_component_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification": { + "name": "notification", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "data": { + "name": "data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'{}'" + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "notification_workspace_id_idx": { + "name": "notification_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notification_workspace_id_workspace_id_fk": { + "name": "notification_workspace_id_workspace_id_fk", + "tableFrom": "notification", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_trigger": { + "name": "notification_trigger", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_id": { + "name": "notification_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "notification_id_monitor_id_crontimestampe": { + "name": "notification_id_monitor_id_crontimestampe", + "columns": [ + "notification_id", + "monitor_id", + "cron_timestamp" + ], + "isUnique": true + } + }, + "foreignKeys": { + "notification_trigger_monitor_id_monitor_id_fk": { + "name": "notification_trigger_monitor_id_monitor_id_fk", + "tableFrom": "notification_trigger", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_trigger_notification_id_notification_id_fk": { + "name": "notification_trigger_notification_id_notification_id_fk", + "tableFrom": "notification_trigger", + "tableTo": "notification", + "columnsFrom": [ + "notification_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notifications_to_monitors": { + "name": "notifications_to_monitors", + "columns": { + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "notification_id": { + "name": "notification_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "notifications_to_monitors_notification_id_idx": { + "name": "notifications_to_monitors_notification_id_idx", + "columns": [ + "notification_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notifications_to_monitors_monitor_id_monitor_id_fk": { + "name": "notifications_to_monitors_monitor_id_monitor_id_fk", + "tableFrom": "notifications_to_monitors", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notifications_to_monitors_notification_id_notification_id_fk": { + "name": "notifications_to_monitors_notification_id_notification_id_fk", + "tableFrom": "notifications_to_monitors", + "tableTo": "notification", + "columnsFrom": [ + "notification_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "notifications_to_monitors_monitor_id_notification_id_pk": { + "columns": [ + "monitor_id", + "notification_id" + ], + "name": "notifications_to_monitors_monitor_id_notification_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_dead_letter": { + "name": "notification_dead_letter", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "outbox_id": { + "name": "outbox_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "dedup_key": { + "name": "dedup_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_id": { + "name": "notification_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "incident_id": { + "name": "incident_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "final_error": { + "name": "final_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "died_at": { + "name": "died_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "notification_dead_letter_dedup_key_idx": { + "name": "notification_dead_letter_dedup_key_idx", + "columns": [ + "dedup_key" + ], + "isUnique": true + }, + "notification_dead_letter_workspace_id_died_at_idx": { + "name": "notification_dead_letter_workspace_id_died_at_idx", + "columns": [ + "workspace_id", + "died_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "notification_dead_letter_monitor_id_monitor_id_fk": { + "name": "notification_dead_letter_monitor_id_monitor_id_fk", + "tableFrom": "notification_dead_letter", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_dead_letter_workspace_id_workspace_id_fk": { + "name": "notification_dead_letter_workspace_id_workspace_id_fk", + "tableFrom": "notification_dead_letter", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "notification_outbox": { + "name": "notification_outbox", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "dedup_key": { + "name": "dedup_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_id": { + "name": "notification_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "incident_id": { + "name": "incident_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "delivery_status": { + "name": "delivery_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deadline_at": { + "name": "deadline_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "locked_by": { + "name": "locked_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "locked_until": { + "name": "locked_until", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "notification_outbox_dedup_key_idx": { + "name": "notification_outbox_dedup_key_idx", + "columns": [ + "dedup_key" + ], + "isUnique": true + }, + "notification_outbox_claim_idx": { + "name": "notification_outbox_claim_idx", + "columns": [ + "next_attempt_at" + ], + "isUnique": false, + "where": "\"notification_outbox\".\"delivery_status\" = 'pending'" + }, + "notification_outbox_notification_id_cron_timestamp_idx": { + "name": "notification_outbox_notification_id_cron_timestamp_idx", + "columns": [ + "notification_id", + "cron_timestamp" + ], + "isUnique": false + }, + "notification_outbox_channel_idx": { + "name": "notification_outbox_channel_idx", + "columns": [ + "monitor_id", + "notification_id" + ], + "isUnique": false, + "where": "\"notification_outbox\".\"delivery_status\" = 'pending'" + } + }, + "foreignKeys": { + "notification_outbox_monitor_id_monitor_id_fk": { + "name": "notification_outbox_monitor_id_monitor_id_fk", + "tableFrom": "notification_outbox", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_outbox_workspace_id_workspace_id_fk": { + "name": "notification_outbox_workspace_id_workspace_id_fk", + "tableFrom": "notification_outbox", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "notification_outbox_notification_id_notification_id_fk": { + "name": "notification_outbox_notification_id_notification_id_fk", + "tableFrom": "notification_outbox", + "tableTo": "notification", + "columnsFrom": [ + "notification_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "notification_outbox_incident_id_monitor_incident_id_fk": { + "name": "notification_outbox_incident_id_monitor_incident_id_fk", + "tableFrom": "notification_outbox", + "tableTo": "monitor_incident", + "columnsFrom": [ + "incident_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_status": { + "name": "monitor_status", + "columns": { + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_status_idx": { + "name": "monitor_status_idx", + "columns": [ + "monitor_id", + "region" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_status_monitor_id_monitor_id_fk": { + "name": "monitor_status_monitor_id_monitor_id_fk", + "tableFrom": "monitor_status", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "monitor_status_monitor_id_region_pk": { + "columns": [ + "monitor_id", + "region" + ], + "name": "monitor_status_monitor_id_region_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invitation": { + "name": "invitation", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "accepted_at": { + "name": "accepted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "invitation_workspace_id_idx": { + "name": "invitation_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_incident": { + "name": "monitor_incident", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'triage'" + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "acknowledged_at": { + "name": "acknowledged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "acknowledged_by": { + "name": "acknowledged_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved_by": { + "name": "resolved_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "incident_screenshot_url": { + "name": "incident_screenshot_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "recovery_screenshot_url": { + "name": "recovery_screenshot_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auto_resolved": { + "name": "auto_resolved", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_incident_workspace_id_started_at_idx": { + "name": "monitor_incident_workspace_id_started_at_idx", + "columns": [ + "workspace_id", + "started_at" + ], + "isUnique": false + }, + "monitor_incident_open_idx": { + "name": "monitor_incident_open_idx", + "columns": [ + "monitor_id" + ], + "isUnique": true, + "where": "\"monitor_incident\".\"resolved_at\" IS NULL" + }, + "monitor_incident_monitor_id_started_at_unique": { + "name": "monitor_incident_monitor_id_started_at_unique", + "columns": [ + "monitor_id", + "started_at" + ], + "isUnique": true + } + }, + "foreignKeys": { + "monitor_incident_monitor_id_monitor_id_fk": { + "name": "monitor_incident_monitor_id_monitor_id_fk", + "tableFrom": "monitor_incident", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set default", + "onUpdate": "no action" + }, + "monitor_incident_workspace_id_workspace_id_fk": { + "name": "monitor_incident_workspace_id_workspace_id_fk", + "tableFrom": "monitor_incident", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "monitor_incident_acknowledged_by_user_id_fk": { + "name": "monitor_incident_acknowledged_by_user_id_fk", + "tableFrom": "monitor_incident", + "tableTo": "user", + "columnsFrom": [ + "acknowledged_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "monitor_incident_resolved_by_user_id_fk": { + "name": "monitor_incident_resolved_by_user_id_fk", + "tableFrom": "monitor_incident", + "tableTo": "user", + "columnsFrom": [ + "resolved_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_tag": { + "name": "monitor_tag", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_tag_workspace_id_idx": { + "name": "monitor_tag_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_tag_workspace_id_workspace_id_fk": { + "name": "monitor_tag_workspace_id_workspace_id_fk", + "tableFrom": "monitor_tag", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_tag_to_monitor": { + "name": "monitor_tag_to_monitor", + "columns": { + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monitor_tag_id": { + "name": "monitor_tag_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_tag_to_monitor_monitor_tag_id_idx": { + "name": "monitor_tag_to_monitor_monitor_tag_id_idx", + "columns": [ + "monitor_tag_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_tag_to_monitor_monitor_id_monitor_id_fk": { + "name": "monitor_tag_to_monitor_monitor_id_monitor_id_fk", + "tableFrom": "monitor_tag_to_monitor", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "monitor_tag_to_monitor_monitor_tag_id_monitor_tag_id_fk": { + "name": "monitor_tag_to_monitor_monitor_tag_id_monitor_tag_id_fk", + "tableFrom": "monitor_tag_to_monitor", + "tableTo": "monitor_tag", + "columnsFrom": [ + "monitor_tag_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "monitor_tag_to_monitor_monitor_id_monitor_tag_id_pk": { + "columns": [ + "monitor_id", + "monitor_tag_id" + ], + "name": "monitor_tag_to_monitor_monitor_id_monitor_tag_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "application": { + "name": "application", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "dsn": { + "name": "dsn", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "application_dsn_unique": { + "name": "application_dsn_unique", + "columns": [ + "dsn" + ], + "isUnique": true + }, + "application_workspace_id_idx": { + "name": "application_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "application_workspace_id_workspace_id_fk": { + "name": "application_workspace_id_workspace_id_fk", + "tableFrom": "application", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "maintenance": { + "name": "maintenance", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from": { + "name": "from", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "to": { + "name": "to", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "maintenance_page_id_idx": { + "name": "maintenance_page_id_idx", + "columns": [ + "page_id" + ], + "isUnique": false + }, + "maintenance_workspace_id_idx": { + "name": "maintenance_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "maintenance_workspace_id_workspace_id_fk": { + "name": "maintenance_workspace_id_workspace_id_fk", + "tableFrom": "maintenance", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "maintenance_page_id_page_id_fk": { + "name": "maintenance_page_id_page_id_fk", + "tableFrom": "maintenance", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "maintenance_created_by_user_id_fk": { + "name": "maintenance_created_by_user_id_fk", + "tableFrom": "maintenance", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "maintenance_updated_by_user_id_fk": { + "name": "maintenance_updated_by_user_id_fk", + "tableFrom": "maintenance", + "tableTo": "user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "check": { + "name": "check", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "regions": { + "name": "regions", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "url": { + "name": "url", + "type": "text(4096)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "headers": { + "name": "headers", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "method": { + "name": "method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'GET'" + }, + "count_requests": { + "name": "count_requests", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 1 + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "check_workspace_id_idx": { + "name": "check_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "check_workspace_id_workspace_id_fk": { + "name": "check_workspace_id_workspace_id_fk", + "tableFrom": "check", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_run": { + "name": "monitor_run", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "runned_at": { + "name": "runned_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_run_workspace_id_created_at_idx": { + "name": "monitor_run_workspace_id_created_at_idx", + "columns": [ + "workspace_id", + "created_at" + ], + "isUnique": false + }, + "monitor_run_monitor_id_idx": { + "name": "monitor_run_monitor_id_idx", + "columns": [ + "monitor_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_run_workspace_id_workspace_id_fk": { + "name": "monitor_run_workspace_id_workspace_id_fk", + "tableFrom": "monitor_run", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "monitor_run_monitor_id_monitor_id_fk": { + "name": "monitor_run_monitor_id_monitor_id_fk", + "tableFrom": "monitor_run", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "private_location_monitor_status": { + "name": "private_location_monitor_status", + "columns": { + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_location_id": { + "name": "private_location_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "private_location_monitor_status_pl_id_idx": { + "name": "private_location_monitor_status_pl_id_idx", + "columns": [ + "private_location_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "private_location_monitor_status_monitor_id_monitor_id_fk": { + "name": "private_location_monitor_status_monitor_id_monitor_id_fk", + "tableFrom": "private_location_monitor_status", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "private_location_monitor_status_private_location_id_private_location_id_fk": { + "name": "private_location_monitor_status_private_location_id_private_location_id_fk", + "tableFrom": "private_location_monitor_status", + "tableTo": "private_location", + "columnsFrom": [ + "private_location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "private_location_monitor_status_monitor_id_private_location_id_pk": { + "columns": [ + "monitor_id", + "private_location_id" + ], + "name": "private_location_monitor_status_monitor_id_private_location_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "private_location": { + "name": "private_location", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'error'" + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "private_location_workspace_id_idx": { + "name": "private_location_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "private_location_workspace_id_workspace_id_fk": { + "name": "private_location_workspace_id_workspace_id_fk", + "tableFrom": "private_location", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "private_location_to_monitor": { + "name": "private_location_to_monitor", + "columns": { + "private_location_id": { + "name": "private_location_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "private_location_to_monitor_private_location_id_idx": { + "name": "private_location_to_monitor_private_location_id_idx", + "columns": [ + "private_location_id" + ], + "isUnique": false + }, + "private_location_to_monitor_monitor_id_idx": { + "name": "private_location_to_monitor_monitor_id_idx", + "columns": [ + "monitor_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "private_location_to_monitor_private_location_id_private_location_id_fk": { + "name": "private_location_to_monitor_private_location_id_private_location_id_fk", + "tableFrom": "private_location_to_monitor", + "tableTo": "private_location", + "columnsFrom": [ + "private_location_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "private_location_to_monitor_monitor_id_monitor_id_fk": { + "name": "private_location_to_monitor_monitor_id_monitor_id_fk", + "tableFrom": "private_location_to_monitor", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_group": { + "name": "monitor_group", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "monitor_group_workspace_id_idx": { + "name": "monitor_group_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + }, + "monitor_group_page_id_idx": { + "name": "monitor_group_page_id_idx", + "columns": [ + "page_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_group_workspace_id_workspace_id_fk": { + "name": "monitor_group_workspace_id_workspace_id_fk", + "tableFrom": "monitor_group", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "monitor_group_page_id_page_id_fk": { + "name": "monitor_group_page_id_page_id_fk", + "tableFrom": "monitor_group", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "viewer": { + "name": "viewer", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "viewer_email_unique": { + "name": "viewer_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "viewer_accounts": { + "name": "viewer_accounts", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "viewer_accounts_user_id_viewer_id_fk": { + "name": "viewer_accounts_user_id_viewer_id_fk", + "tableFrom": "viewer_accounts", + "tableTo": "viewer", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "viewer_accounts_provider_providerAccountId_pk": { + "columns": [ + "provider", + "providerAccountId" + ], + "name": "viewer_accounts_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "viewer_session": { + "name": "viewer_session", + "columns": { + "session_token": { + "name": "session_token", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "viewer_session_user_id_viewer_id_fk": { + "name": "viewer_session_user_id_viewer_id_fk", + "tableFrom": "viewer_session", + "tableTo": "viewer", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "api_key": { + "name": "api_key", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "prefix": { + "name": "prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "hashed_token": { + "name": "hashed_token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by_id": { + "name": "created_by_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[\"write\"]'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "api_key_prefix_unique": { + "name": "api_key_prefix_unique", + "columns": [ + "prefix" + ], + "isUnique": true + }, + "api_key_hashed_token_unique": { + "name": "api_key_hashed_token_unique", + "columns": [ + "hashed_token" + ], + "isUnique": true + }, + "api_key_prefix_idx": { + "name": "api_key_prefix_idx", + "columns": [ + "prefix" + ], + "isUnique": false + }, + "api_key_workspace_id_idx": { + "name": "api_key_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "api_key_workspace_id_workspace_id_fk": { + "name": "api_key_workspace_id_workspace_id_fk", + "tableFrom": "api_key", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "api_key_created_by_id_user_id_fk": { + "name": "api_key_created_by_id_user_id_fk", + "tableFrom": "api_key", + "tableTo": "user", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "maintenance_to_page_component": { + "name": "maintenance_to_page_component", + "columns": { + "maintenance_id": { + "name": "maintenance_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_component_id": { + "name": "page_component_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "maintenance_to_page_component_page_component_id_idx": { + "name": "maintenance_to_page_component_page_component_id_idx", + "columns": [ + "page_component_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "maintenance_to_page_component_maintenance_id_maintenance_id_fk": { + "name": "maintenance_to_page_component_maintenance_id_maintenance_id_fk", + "tableFrom": "maintenance_to_page_component", + "tableTo": "maintenance", + "columnsFrom": [ + "maintenance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "maintenance_to_page_component_page_component_id_page_component_id_fk": { + "name": "maintenance_to_page_component_page_component_id_page_component_id_fk", + "tableFrom": "maintenance_to_page_component", + "tableTo": "page_component", + "columnsFrom": [ + "page_component_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "maintenance_to_page_component_maintenance_id_page_component_id_pk": { + "columns": [ + "maintenance_id", + "page_component_id" + ], + "name": "maintenance_to_page_component_maintenance_id_page_component_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "page_component": { + "name": "page_component", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'monitor'" + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "group_id": { + "name": "group_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "group_order": { + "name": "group_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "page_component_workspace_id_idx": { + "name": "page_component_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + }, + "page_component_page_id_monitor_id_unique": { + "name": "page_component_page_id_monitor_id_unique", + "columns": [ + "page_id", + "monitor_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "page_component_workspace_id_workspace_id_fk": { + "name": "page_component_workspace_id_workspace_id_fk", + "tableFrom": "page_component", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "page_component_page_id_page_id_fk": { + "name": "page_component_page_id_page_id_fk", + "tableFrom": "page_component", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "page_component_monitor_id_monitor_id_fk": { + "name": "page_component_monitor_id_monitor_id_fk", + "tableFrom": "page_component", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "page_component_group_id_page_component_groups_id_fk": { + "name": "page_component_group_id_page_component_groups_id_fk", + "tableFrom": "page_component", + "tableTo": "page_component_groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": { + "page_component_type_check": { + "name": "page_component_type_check", + "value": "\"page_component\".\"type\" = 'monitor' AND \"page_component\".\"monitor_id\" IS NOT NULL OR \"page_component\".\"type\" = 'static' AND \"page_component\".\"monitor_id\" IS NULL" + } + } + }, + "status_report_update_to_page_component": { + "name": "status_report_update_to_page_component", + "columns": { + "status_report_update_id": { + "name": "status_report_update_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_component_id": { + "name": "page_component_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "impact": { + "name": "impact", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "status_report_update_to_page_component_page_component_id_idx": { + "name": "status_report_update_to_page_component_page_component_id_idx", + "columns": [ + "page_component_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "status_report_update_to_page_component_status_report_update_id_status_report_update_id_fk": { + "name": "status_report_update_to_page_component_status_report_update_id_status_report_update_id_fk", + "tableFrom": "status_report_update_to_page_component", + "tableTo": "status_report_update", + "columnsFrom": [ + "status_report_update_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_report_update_to_page_component_page_component_id_page_component_id_fk": { + "name": "status_report_update_to_page_component_page_component_id_page_component_id_fk", + "tableFrom": "status_report_update_to_page_component", + "tableTo": "page_component", + "columnsFrom": [ + "page_component_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "status_report_update_to_page_component_status_report_update_id_page_component_id_pk": { + "columns": [ + "status_report_update_id", + "page_component_id" + ], + "name": "status_report_update_to_page_component_status_report_update_id_page_component_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "status_report_to_page_component": { + "name": "status_report_to_page_component", + "columns": { + "status_report_id": { + "name": "status_report_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_component_id": { + "name": "page_component_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "status_report_to_page_component_page_component_id_idx": { + "name": "status_report_to_page_component_page_component_id_idx", + "columns": [ + "page_component_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "status_report_to_page_component_status_report_id_status_report_id_fk": { + "name": "status_report_to_page_component_status_report_id_status_report_id_fk", + "tableFrom": "status_report_to_page_component", + "tableTo": "status_report", + "columnsFrom": [ + "status_report_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "status_report_to_page_component_page_component_id_page_component_id_fk": { + "name": "status_report_to_page_component_page_component_id_page_component_id_fk", + "tableFrom": "status_report_to_page_component", + "tableTo": "page_component", + "columnsFrom": [ + "page_component_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "status_report_to_page_component_status_report_id_page_component_id_pk": { + "columns": [ + "status_report_id", + "page_component_id" + ], + "name": "status_report_to_page_component_status_report_id_page_component_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "page_component_groups": { + "name": "page_component_groups", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "page_id": { + "name": "page_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "default_open": { + "name": "default_open", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "page_component_groups_page_id_idx": { + "name": "page_component_groups_page_id_idx", + "columns": [ + "page_id" + ], + "isUnique": false + }, + "page_component_groups_workspace_id_idx": { + "name": "page_component_groups_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "page_component_groups_workspace_id_workspace_id_fk": { + "name": "page_component_groups_workspace_id_workspace_id_fk", + "tableFrom": "page_component_groups", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "page_component_groups_page_id_page_id_fk": { + "name": "page_component_groups_page_id_page_id_fk", + "tableFrom": "page_component_groups", + "tableTo": "page", + "columnsFrom": [ + "page_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback": { + "name": "feedback", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "blocker": { + "name": "blocker", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "feedback_workspace_id_idx": { + "name": "feedback_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "feedback_workspace_id_workspace_id_fk": { + "name": "feedback_workspace_id_workspace_id_fk", + "tableFrom": "feedback", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "feedback_user_id_user_id_fk": { + "name": "feedback_user_id_user_id_fk", + "tableFrom": "feedback", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "audit_log": { + "name": "audit_log", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "actor_user_id": { + "name": "actor_user_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_type": { + "name": "entity_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "entity_id": { + "name": "entity_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "before": { + "name": "before", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "after": { + "name": "after", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "changed_fields": { + "name": "changed_fields", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "audit_log_workspace_created_idx": { + "name": "audit_log_workspace_created_idx", + "columns": [ + "workspace_id", + "created_at" + ], + "isUnique": false + }, + "audit_log_entity_idx": { + "name": "audit_log_entity_idx", + "columns": [ + "workspace_id", + "entity_type", + "entity_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "external_service": { + "name": "external_service", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(json_array())" + }, + "name": { + "name": "name", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status_page_url": { + "name": "status_page_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "industry": { + "name": "industry", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "api_config": { + "name": "api_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "external_service_slug_unique": { + "name": "external_service_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "external_service_deleted_at_idx": { + "name": "external_service_deleted_at_idx", + "columns": [ + "deleted_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "external_service_component": { + "name": "external_service_component", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "external_service_id": { + "name": "external_service_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "upstream_component_id": { + "name": "upstream_component_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "aliases": { + "name": "aliases", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(json_array())" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "group_name": { + "name": "group_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "indicator": { + "name": "indicator", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "external_service_component_unique_idx": { + "name": "external_service_component_unique_idx", + "columns": [ + "external_service_id", + "upstream_component_id" + ], + "isUnique": true + }, + "external_service_component_slug_unique_idx": { + "name": "external_service_component_slug_unique_idx", + "columns": [ + "external_service_id", + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "external_service_component_external_service_id_external_service_id_fk": { + "name": "external_service_component_external_service_id_external_service_id_fk", + "tableFrom": "external_service_component", + "tableTo": "external_service", + "columnsFrom": [ + "external_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "external_service_incident": { + "name": "external_service_incident", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "external_service_id": { + "name": "external_service_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_incident_id": { + "name": "provider_incident_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "impact": { + "name": "impact", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shortlink": { + "name": "shortlink", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "affected_component_ids": { + "name": "affected_component_ids", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "raw_payload": { + "name": "raw_payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "raw_payload_purged_at": { + "name": "raw_payload_purged_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_seen_at": { + "name": "first_seen_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "external_service_incident_unique_idx": { + "name": "external_service_incident_unique_idx", + "columns": [ + "external_service_id", + "provider_incident_id" + ], + "isUnique": true + }, + "external_service_incident_started_at_idx": { + "name": "external_service_incident_started_at_idx", + "columns": [ + "external_service_id", + "started_at" + ], + "isUnique": false + }, + "external_service_incident_resolved_at_idx": { + "name": "external_service_incident_resolved_at_idx", + "columns": [ + "resolved_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "external_service_incident_external_service_id_external_service_id_fk": { + "name": "external_service_incident_external_service_id_external_service_id_fk", + "tableFrom": "external_service_incident", + "tableTo": "external_service", + "columnsFrom": [ + "external_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "external_service_report": { + "name": "external_service_report", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "external_service_id": { + "name": "external_service_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "external_service_component_id": { + "name": "external_service_component_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reporter_hash": { + "name": "reporter_hash", + "type": "text(64)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "country": { + "name": "country", + "type": "text(2)", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "external_service_report_service_idx": { + "name": "external_service_report_service_idx", + "columns": [ + "external_service_id", + "created_at" + ], + "isUnique": false + }, + "external_service_report_component_idx": { + "name": "external_service_report_component_idx", + "columns": [ + "external_service_component_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "external_service_report_external_service_id_external_service_id_fk": { + "name": "external_service_report_external_service_id_external_service_id_fk", + "tableFrom": "external_service_report", + "tableTo": "external_service", + "columnsFrom": [ + "external_service_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "external_service_report_external_service_component_id_external_service_component_id_fk": { + "name": "external_service_report_external_service_component_id_external_service_component_id_fk", + "tableFrom": "external_service_report", + "tableTo": "external_service_component", + "columnsFrom": [ + "external_service_component_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "chat_session": { + "name": "chat_session", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "messages": { + "name": "messages", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "chat_session_workspace_user_updated_idx": { + "name": "chat_session_workspace_user_updated_idx", + "columns": [ + "workspace_id", + "user_id", + "updated_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "chat_session_workspace_id_workspace_id_fk": { + "name": "chat_session_workspace_id_workspace_id_fk", + "tableFrom": "chat_session", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "chat_session_user_id_user_id_fk": { + "name": "chat_session_user_id_user_id_fk", + "tableFrom": "chat_session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "frozen_monitor_uptime": { + "name": "frozen_monitor_uptime", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "month": { + "name": "month", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "days": { + "name": "days", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "frozen_monitor_uptime_workspace_id_idx": { + "name": "frozen_monitor_uptime_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + }, + "frozen_monitor_uptime_monitor_id_month_unique": { + "name": "frozen_monitor_uptime_monitor_id_month_unique", + "columns": [ + "monitor_id", + "month" + ], + "isUnique": true + } + }, + "foreignKeys": { + "frozen_monitor_uptime_workspace_id_workspace_id_fk": { + "name": "frozen_monitor_uptime_workspace_id_workspace_id_fk", + "tableFrom": "frozen_monitor_uptime", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "frozen_monitor_uptime_monitor_id_monitor_id_fk": { + "name": "frozen_monitor_uptime_monitor_id_monitor_id_fk", + "tableFrom": "frozen_monitor_uptime", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "monitor_transition": { + "name": "monitor_transition", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "monitor_id": { + "name": "monitor_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cron_timestamp": { + "name": "cron_timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_status": { + "name": "from_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "to_status": { + "name": "to_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "quorum_count": { + "name": "quorum_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region_count": { + "name": "region_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "transitioned": { + "name": "transitioned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "outbox_rows": { + "name": "outbox_rows", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "monitor_transition_monitor_id_cron_timestamp_idx": { + "name": "monitor_transition_monitor_id_cron_timestamp_idx", + "columns": [ + "monitor_id", + "cron_timestamp" + ], + "isUnique": false + }, + "monitor_transition_created_at_idx": { + "name": "monitor_transition_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "monitor_transition_monitor_id_monitor_id_fk": { + "name": "monitor_transition_monitor_id_monitor_id_fk", + "tableFrom": "monitor_transition", + "tableTo": "monitor", + "columnsFrom": [ + "monitor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_client": { + "name": "oauth_client", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'MCP Client'" + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "oauth_client_client_id_unique": { + "name": "oauth_client_client_id_unique", + "columns": [ + "client_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_session": { + "name": "oauth_session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resource": { + "name": "resource", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code_challenge_method": { + "name": "code_challenge_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'S256'" + }, + "decided_at": { + "name": "decided_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauth_session_client_id_idx": { + "name": "oauth_session_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauth_session_expires_at_idx": { + "name": "oauth_session_expires_at_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_session_client_id_oauth_client_client_id_fk": { + "name": "oauth_session_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_session", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_authorization_code": { + "name": "oauth_authorization_code", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "hash": { + "name": "hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "consumed_at": { + "name": "consumed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "grant_id": { + "name": "grant_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "oauth_authorization_code_hash_unique": { + "name": "oauth_authorization_code_hash_unique", + "columns": [ + "hash" + ], + "isUnique": true + }, + "oauth_authorization_code_expires_at_idx": { + "name": "oauth_authorization_code_expires_at_idx", + "columns": [ + "expires_at" + ], + "isUnique": false + }, + "oauth_authorization_code_client_id_idx": { + "name": "oauth_authorization_code_client_id_idx", + "columns": [ + "client_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_authorization_code_client_id_oauth_client_client_id_fk": { + "name": "oauth_authorization_code_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_authorization_code", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_authorization_code_user_id_user_id_fk": { + "name": "oauth_authorization_code_user_id_user_id_fk", + "tableFrom": "oauth_authorization_code", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_authorization_code_workspace_id_workspace_id_fk": { + "name": "oauth_authorization_code_workspace_id_workspace_id_fk", + "tableFrom": "oauth_authorization_code", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_authorization_code_grant_id_oauth_grant_id_fk": { + "name": "oauth_authorization_code_grant_id_oauth_grant_id_fk", + "tableFrom": "oauth_authorization_code", + "tableTo": "oauth_grant", + "columnsFrom": [ + "grant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_grant": { + "name": "oauth_grant", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_hash": { + "name": "access_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_hash": { + "name": "refresh_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "previous_refresh_token_hash": { + "name": "previous_refresh_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "rotated_at": { + "name": "rotated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "oauth_grant_access_token_hash_unique": { + "name": "oauth_grant_access_token_hash_unique", + "columns": [ + "access_token_hash" + ], + "isUnique": true + }, + "oauth_grant_refresh_token_hash_unique": { + "name": "oauth_grant_refresh_token_hash_unique", + "columns": [ + "refresh_token_hash" + ], + "isUnique": true + }, + "oauth_grant_workspace_id_idx": { + "name": "oauth_grant_workspace_id_idx", + "columns": [ + "workspace_id" + ], + "isUnique": false + }, + "oauth_grant_client_id_user_id_idx": { + "name": "oauth_grant_client_id_user_id_idx", + "columns": [ + "client_id", + "user_id" + ], + "isUnique": false + }, + "oauth_grant_user_id_idx": { + "name": "oauth_grant_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "oauth_grant_previous_refresh_token_hash_idx": { + "name": "oauth_grant_previous_refresh_token_hash_idx", + "columns": [ + "previous_refresh_token_hash" + ], + "isUnique": false + }, + "oauth_grant_refresh_token_expires_at_idx": { + "name": "oauth_grant_refresh_token_expires_at_idx", + "columns": [ + "refresh_token_expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_grant_client_id_oauth_client_client_id_fk": { + "name": "oauth_grant_client_id_oauth_client_client_id_fk", + "tableFrom": "oauth_grant", + "tableTo": "oauth_client", + "columnsFrom": [ + "client_id" + ], + "columnsTo": [ + "client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_grant_user_id_user_id_fk": { + "name": "oauth_grant_user_id_user_id_fk", + "tableFrom": "oauth_grant", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "oauth_grant_workspace_id_workspace_id_fk": { + "name": "oauth_grant_workspace_id_workspace_id_fk", + "tableFrom": "oauth_grant", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "incident": { + "name": "incident", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text(256)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'open'" + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "commander_id": { + "name": "commander_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "declared_by": { + "name": "declared_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "declared_at": { + "name": "declared_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mitigated_at": { + "name": "mitigated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved_at": { + "name": "resolved_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "resolved_by": { + "name": "resolved_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "closed_at": { + "name": "closed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_report_id": { + "name": "status_report_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slack_team_id": { + "name": "slack_team_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "incident_workspace_id_status_idx": { + "name": "incident_workspace_id_status_idx", + "columns": [ + "workspace_id", + "status" + ], + "isUnique": false + }, + "incident_workspace_id_declared_at_idx": { + "name": "incident_workspace_id_declared_at_idx", + "columns": [ + "workspace_id", + "declared_at" + ], + "isUnique": false + }, + "incident_slack_channel_idx": { + "name": "incident_slack_channel_idx", + "columns": [ + "slack_team_id", + "slack_channel_id" + ], + "isUnique": true, + "where": "\"incident\".\"slack_channel_id\" IS NOT NULL" + }, + "incident_status_report_id_idx": { + "name": "incident_status_report_id_idx", + "columns": [ + "status_report_id" + ], + "isUnique": true, + "where": "\"incident\".\"status_report_id\" IS NOT NULL" + } + }, + "foreignKeys": { + "incident_workspace_id_workspace_id_fk": { + "name": "incident_workspace_id_workspace_id_fk", + "tableFrom": "incident", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_commander_id_user_id_fk": { + "name": "incident_commander_id_user_id_fk", + "tableFrom": "incident", + "tableTo": "user", + "columnsFrom": [ + "commander_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_declared_by_user_id_fk": { + "name": "incident_declared_by_user_id_fk", + "tableFrom": "incident", + "tableTo": "user", + "columnsFrom": [ + "declared_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_resolved_by_user_id_fk": { + "name": "incident_resolved_by_user_id_fk", + "tableFrom": "incident", + "tableTo": "user", + "columnsFrom": [ + "resolved_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_status_report_id_status_report_id_fk": { + "name": "incident_status_report_id_status_report_id_fk", + "tableFrom": "incident", + "tableTo": "status_report", + "columnsFrom": [ + "status_report_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "incident_event": { + "name": "incident_event", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "incident_id": { + "name": "incident_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "incident_event_incident_id_created_at_idx": { + "name": "incident_event_incident_id_created_at_idx", + "columns": [ + "incident_id", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "incident_event_incident_id_incident_id_fk": { + "name": "incident_event_incident_id_incident_id_fk", + "tableFrom": "incident_event", + "tableTo": "incident", + "columnsFrom": [ + "incident_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "incident_event_created_by_user_id_fk": { + "name": "incident_event_created_by_user_id_fk", + "tableFrom": "incident_event", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "incident_postmortem": { + "name": "incident_postmortem", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "incident_id": { + "name": "incident_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_transcript": { + "name": "source_transcript", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "drafted_by": { + "name": "drafted_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_by": { + "name": "created_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_by": { + "name": "updated_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "approved_by": { + "name": "approved_by", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "approved_at": { + "name": "approved_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "published_at": { + "name": "published_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "incident_postmortem_incident_id_unique": { + "name": "incident_postmortem_incident_id_unique", + "columns": [ + "incident_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "incident_postmortem_incident_id_incident_id_fk": { + "name": "incident_postmortem_incident_id_incident_id_fk", + "tableFrom": "incident_postmortem", + "tableTo": "incident", + "columnsFrom": [ + "incident_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "incident_postmortem_created_by_user_id_fk": { + "name": "incident_postmortem_created_by_user_id_fk", + "tableFrom": "incident_postmortem", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_postmortem_updated_by_user_id_fk": { + "name": "incident_postmortem_updated_by_user_id_fk", + "tableFrom": "incident_postmortem", + "tableTo": "user", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "incident_postmortem_approved_by_user_id_fk": { + "name": "incident_postmortem_approved_by_user_id_fk", + "tableFrom": "incident_postmortem", + "tableTo": "user", + "columnsFrom": [ + "approved_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slack_user": { + "name": "slack_user", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "workspace_id": { + "name": "workspace_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slack_team_id": { + "name": "slack_team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(strftime('%s', 'now'))" + } + }, + "indexes": { + "slack_user_user_id_idx": { + "name": "slack_user_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "slack_user_workspace_id_slack_team_id_slack_user_id_unique": { + "name": "slack_user_workspace_id_slack_team_id_slack_user_id_unique", + "columns": [ + "workspace_id", + "slack_team_id", + "slack_user_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "slack_user_workspace_id_workspace_id_fk": { + "name": "slack_user_workspace_id_workspace_id_fk", + "tableFrom": "slack_user", + "tableTo": "workspace", + "columnsFrom": [ + "workspace_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_user_user_id_user_id_fk": { + "name": "slack_user_user_id_user_id_fk", + "tableFrom": "slack_user", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": { + "page_lower_slug_idx": { + "columns": { + "LOWER(\"slug\")": { + "isExpression": true + } + } + }, + "page_lower_custom_domain_idx": { + "columns": { + "LOWER(\"custom_domain\")": { + "isExpression": true + } + } + }, + "idx_page_subscriber_email_page_active": { + "columns": { + "LOWER(\"email\")": { + "isExpression": true + } + } + }, + "idx_page_subscriber_webhook_page_active": { + "columns": { + "LOWER(\"webhook_url\")": { + "isExpression": true + } + } + } + } + } +} \ No newline at end of file diff --git a/packages/db/drizzle/meta/_journal.json b/packages/db/drizzle/meta/_journal.json index 9616a1a3b..8e21a5033 100644 --- a/packages/db/drizzle/meta/_journal.json +++ b/packages/db/drizzle/meta/_journal.json @@ -638,6 +638,13 @@ "when": 1790604809096, "tag": "0090_condemned_photon", "breakpoints": true + }, + { + "idx": 91, + "version": "6", + "when": 1790864737627, + "tag": "0091_amused_wrecker", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema/maintenances/maintenance.ts b/packages/db/src/schema/maintenances/maintenance.ts index ffe802a8f..f7ea93240 100644 --- a/packages/db/src/schema/maintenances/maintenance.ts +++ b/packages/db/src/schema/maintenances/maintenance.ts @@ -3,6 +3,7 @@ import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; import { maintenancesToPageComponents } from "../page_components"; import { page } from "../pages"; +import { user } from "../users"; import { workspace } from "../workspaces"; export const maintenance = sqliteTable( @@ -20,6 +21,9 @@ export const maintenance = sqliteTable( onDelete: "cascade", }), + createdBy: integer("created_by").references(() => user.id), + updatedBy: integer("updated_by").references(() => user.id), + createdAt: integer("created_at", { mode: "timestamp" }).default( sql`(strftime('%s', 'now'))`, ), @@ -43,4 +47,14 @@ export const maintenanceRelations = relations(maintenance, ({ one, many }) => ({ fields: [maintenance.workspaceId], references: [workspace.id], }), + createdByUser: one(user, { + fields: [maintenance.createdBy], + references: [user.id], + relationName: "maintenanceCreatedBy", + }), + updatedByUser: one(user, { + fields: [maintenance.updatedBy], + references: [user.id], + relationName: "maintenanceUpdatedBy", + }), })); diff --git a/packages/db/src/schema/shared.ts b/packages/db/src/schema/shared.ts index 3e2a1c7dc..8d16dc989 100644 --- a/packages/db/src/schema/shared.ts +++ b/packages/db/src/schema/shared.ts @@ -33,38 +33,45 @@ export const selectPublicMonitorSchema = name: data.externalName || data.name, })); -export const selectStatusReportPageSchema = selectStatusReportSchema.extend({ - statusReportUpdates: z - .array( - selectStatusReportUpdateSchema.extend({ - statusReportUpdateToPageComponents: z - .array(selectStatusReportUpdateToPageComponentSchema) - .prefault([]), - }), - ) - .prefault([]), - statusReportsToPageComponents: z - .array( - z.object({ - pageComponentId: z.number(), - statusReportId: z.number(), - pageComponent: selectPageComponentSchema, - }), - ) - .prefault([]), -}); +// author identity is workspace-internal; the public page never carries it +const authorColumns = { createdBy: true, updatedBy: true } as const; -export const selectMaintenancePageSchema = selectMaintenanceSchema.extend({ - maintenancesToPageComponents: z - .array( - z.object({ - pageComponentId: z.number(), - maintenanceId: z.number(), - pageComponent: selectPageComponentSchema, - }), - ) - .prefault([]), -}); +export const selectStatusReportPageSchema = selectStatusReportSchema + .omit(authorColumns) + .extend({ + statusReportUpdates: z + .array( + selectStatusReportUpdateSchema.omit(authorColumns).extend({ + statusReportUpdateToPageComponents: z + .array(selectStatusReportUpdateToPageComponentSchema) + .prefault([]), + }), + ) + .prefault([]), + statusReportsToPageComponents: z + .array( + z.object({ + pageComponentId: z.number(), + statusReportId: z.number(), + pageComponent: selectPageComponentSchema, + }), + ) + .prefault([]), + }); + +export const selectMaintenancePageSchema = selectMaintenanceSchema + .omit(authorColumns) + .extend({ + maintenancesToPageComponents: z + .array( + z.object({ + pageComponentId: z.number(), + maintenanceId: z.number(), + pageComponent: selectPageComponentSchema, + }), + ) + .prefault([]), + }); export const selectPageSchemaWithRelation = selectPageSchema.extend({ monitors: z.array(selectMonitorSchema), diff --git a/packages/db/src/schema/status_reports/status_reports.ts b/packages/db/src/schema/status_reports/status_reports.ts index 4c245a745..5fed594f2 100644 --- a/packages/db/src/schema/status_reports/status_reports.ts +++ b/packages/db/src/schema/status_reports/status_reports.ts @@ -6,6 +6,7 @@ import { statusReportsToPageComponents, } from "../page_components"; import { page } from "../pages"; +import { user } from "../users"; import { workspace } from "../workspaces"; import { statusReportStatus } from "./constants"; @@ -24,6 +25,9 @@ export const statusReport = sqliteTable( onDelete: "cascade", }), + createdBy: integer("created_by").references(() => user.id), + updatedBy: integer("updated_by").references(() => user.id), + createdAt: integer("created_at", { mode: "timestamp" }).default( sql`(strftime('%s', 'now'))`, ), @@ -49,6 +53,10 @@ export const statusReportUpdate = sqliteTable( statusReportId: integer("status_report_id") .references(() => statusReport.id, { onDelete: "cascade" }) .notNull(), + + createdBy: integer("created_by").references(() => user.id), + updatedBy: integer("updated_by").references(() => user.id), + createdAt: integer("created_at", { mode: "timestamp" }).default( sql`(strftime('%s', 'now'))`, ), @@ -74,6 +82,16 @@ export const StatusReportRelations = relations( fields: [statusReport.workspaceId], references: [workspace.id], }), + createdByUser: one(user, { + fields: [statusReport.createdBy], + references: [user.id], + relationName: "statusReportCreatedBy", + }), + updatedByUser: one(user, { + fields: [statusReport.updatedBy], + references: [user.id], + relationName: "statusReportUpdatedBy", + }), }), ); @@ -87,5 +105,15 @@ export const statusReportUpdateRelations = relations( statusReportUpdateToPageComponents: many( statusReportUpdateToPageComponents, ), + createdByUser: one(user, { + fields: [statusReportUpdate.createdBy], + references: [user.id], + relationName: "statusReportUpdateCreatedBy", + }), + updatedByUser: one(user, { + fields: [statusReportUpdate.updatedBy], + references: [user.id], + relationName: "statusReportUpdateUpdatedBy", + }), }), ); diff --git a/packages/services/src/agent-tools/incident.ts b/packages/services/src/agent-tools/incident.ts index f6b371596..0370361ee 100644 --- a/packages/services/src/agent-tools/incident.ts +++ b/packages/services/src/agent-tools/incident.ts @@ -4,6 +4,7 @@ import { } from "@openstatus/db/src/schema/incidents/constants"; import { z } from "zod"; +import { attributedUserSchema, toAttributedUser } from "../attribution"; import { tryGetActorUserId } from "../context"; import { NotFoundError } from "../errors"; import { @@ -11,7 +12,6 @@ import { approvePostmortem, declareIncident, draftPostmortem, - displayName, getIncident, getPostmortem, listIncidentEvents, @@ -27,27 +27,13 @@ const title = z.string().trim().min(1).max(256); const summary = z.string().trim().min(1).max(4000); const note = z.string().trim().min(1).max(10_000); -const personSchema = z.object({ id: z.number().int(), name: z.string() }); - -type Person = { - id: number; - name: string | null; - firstName: string | null; - lastName: string | null; - email: string | null; -} | null; - -function person(row: Person) { - return row ? { id: row.id, name: displayName(row) } : null; -} - const IncidentSummary = z.object({ id: z.number().int(), title: z.string(), severity: z.enum(incidentSeverity), status: z.enum(incidentStatus), closed: z.boolean(), - commander: personSchema.nullable(), + commander: attributedUserSchema.nullable(), declaredAt: z.string(), startedAt: z.string(), statusReportId: z.number().int().nullable(), @@ -88,7 +74,7 @@ export const listIncidentsTool: AgentTool< severity: row.severity, status: row.status, closed: row.closedAt !== null, - commander: person(row.commander), + commander: toAttributedUser(row.commander), declaredAt: row.declaredAt.toISOString(), startedAt: row.startedAt.toISOString(), statusReportId: row.statusReportId, @@ -104,7 +90,7 @@ const GetIncidentInput = z.object({ const GetIncidentOutput = IncidentSummary.extend({ summary: z.string().nullable(), - declaredBy: personSchema.nullable(), + declaredBy: attributedUserSchema.nullable(), statusReport: z .object({ id: z.number().int(), title: z.string(), status: z.string() }) .nullable(), @@ -113,7 +99,7 @@ const GetIncidentOutput = IncidentSummary.extend({ type: z.string(), message: z.string().nullable(), at: z.string(), - by: personSchema.nullable(), + by: attributedUserSchema.nullable(), }), ), }); @@ -143,13 +129,13 @@ export const getIncidentTool: AgentTool< severity: row.severity, status: row.status, closed: row.closedAt !== null, - commander: person(row.commander), + commander: toAttributedUser(row.commander), declaredAt: row.declaredAt.toISOString(), startedAt: row.startedAt.toISOString(), statusReportId: row.statusReportId, slackChannelId: row.slackChannelId, summary: row.summary, - declaredBy: person(row.declaredByUser), + declaredBy: toAttributedUser(row.declaredByUser), statusReport: row.statusReport ? { id: row.statusReport.id, @@ -161,7 +147,7 @@ export const getIncidentTool: AgentTool< type: e.type, message: e.message, at: e.createdAt.toISOString(), - by: person(e.createdByUser), + by: toAttributedUser(e.createdByUser), })), }; }, diff --git a/packages/services/src/agent-tools/maintenance.ts b/packages/services/src/agent-tools/maintenance.ts index 8934efc5f..95542ebb8 100644 --- a/packages/services/src/agent-tools/maintenance.ts +++ b/packages/services/src/agent-tools/maintenance.ts @@ -1,5 +1,6 @@ import { z } from "zod"; +import { attributedUserSchema } from "../attribution"; import { createMaintenance, listMaintenances, @@ -57,6 +58,8 @@ const ListMaintenancesOutput = z.object({ to: z.string(), pageId: z.number().int().nullable(), pageComponentIds: z.array(z.number().int()), + createdBy: attributedUserSchema.nullable(), + updatedBy: attributedUserSchema.nullable(), }), ), pagination: z.object({ @@ -98,6 +101,8 @@ export const listMaintenancesTool: AgentTool< to: m.to.toISOString(), pageId: m.pageId, pageComponentIds: m.pageComponentIds, + createdBy: m.createdByUser, + updatedBy: m.updatedByUser, })), pagination: { page, diff --git a/packages/services/src/agent-tools/status-report.ts b/packages/services/src/agent-tools/status-report.ts index 10b4821c9..a80faf8f0 100644 --- a/packages/services/src/agent-tools/status-report.ts +++ b/packages/services/src/agent-tools/status-report.ts @@ -4,6 +4,7 @@ import { } from "@openstatus/db/src/schema"; import { z } from "zod"; +import { attributedUserSchema } from "../attribution"; import type { ServiceContext } from "../context"; import { addStatusReportUpdate, @@ -112,11 +113,14 @@ const ListStatusReportsOutput = z.object({ pageId: z.number().int().nullable(), createdAt: z.string().nullable(), updatedAt: z.string().nullable(), + createdBy: attributedUserSchema.nullable(), + updatedBy: attributedUserSchema.nullable(), latestUpdate: z .object({ message: z.string(), status: statusReportStatusSchema, date: z.string().nullable(), + createdBy: attributedUserSchema.nullable(), }) .nullable(), }), @@ -162,11 +166,14 @@ export const listStatusReportsTool: AgentTool< pageId: r.pageId, createdAt: r.createdAt?.toISOString() ?? null, updatedAt: r.updatedAt?.toISOString() ?? null, + createdBy: r.createdByUser, + updatedBy: r.updatedByUser, latestUpdate: latestUpdate ? { message: latestUpdate.message, status: latestUpdate.status, date: latestUpdate.date?.toISOString() ?? null, + createdBy: latestUpdate.createdByUser, } : null, }; diff --git a/packages/services/src/attribution.ts b/packages/services/src/attribution.ts new file mode 100644 index 000000000..acb4777f0 --- /dev/null +++ b/packages/services/src/attribution.ts @@ -0,0 +1,73 @@ +import { inArray } from "@openstatus/db"; +import { user } from "@openstatus/db/src/schema"; +import { z } from "zod"; + +import type { DB } from "./context"; + +export const attributedUserSchema = z.object({ + id: z.number().int(), + name: z.string(), +}); + +/** The user behind a `created_by` / `updated_by` column, reduced to what callers may show. */ +export type AttributedUser = z.infer; + +type UserRow = { + id: number; + name: string | null; + firstName: string | null; + lastName: string | null; + email: string | null; + deletedAt?: Date | null; +}; + +export function displayName(row: { + name: string | null; + firstName: string | null; + lastName: string | null; + email: string | null; +}): string { + const full = [row.firstName, row.lastName].filter(Boolean).join(" "); + return row.name || full || row.email || "Unknown user"; +} + +// `user/delete.ts` soft-deletes and blanks every name field, so the row +// survives but would otherwise render as "Unknown user". +export function toAttributedUser( + row: UserRow | null | undefined, +): AttributedUser | null { + if (!row) return null; + return { + id: row.id, + name: row.deletedAt ? "Deleted user" : displayName(row), + }; +} + +/** One query for every distinct id; ids with no user row are absent from the map. */ +export async function loadAttributedUsers( + db: DB, + ids: Iterable, +): Promise> { + const distinct = [ + ...new Set([...ids].filter((id): id is number => id != null)), + ]; + const map = new Map(); + if (distinct.length === 0) return map; + const rows = await db + .select({ + id: user.id, + name: user.name, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + deletedAt: user.deletedAt, + }) + .from(user) + .where(inArray(user.id, distinct)) + .all(); + for (const row of rows) { + const attributed = toAttributedUser(row); + if (attributed) map.set(row.id, attributed); + } + return map; +} diff --git a/packages/services/src/import/__tests__/attribution.test.ts b/packages/services/src/import/__tests__/attribution.test.ts new file mode 100644 index 000000000..6385cf854 --- /dev/null +++ b/packages/services/src/import/__tests__/attribution.test.ts @@ -0,0 +1,136 @@ +import { eq } from "@openstatus/db"; +import { + maintenance, + page, + statusReport, + statusReportUpdate, +} from "@openstatus/db/src/schema"; +import type { PhaseResult } from "@openstatus/importers"; +import { expect } from "@std/expect"; +import { beforeAll, describe, test } from "@std/testing/bdd"; + +import { + createWorkspaceFixture, + makeUserCtx, + withTestTransaction, +} from "../../../test/helpers"; +import type { ServiceContext } from "../../context"; +import { writeIncidentsPhase, writeMaintenancesPhase } from "../phase-writers"; + +const TEST_PREFIX = "svc-import-attribution"; + +let teamCtx: ServiceContext; +let ownerId: number; + +beforeAll(async () => { + const fixture = await createWorkspaceFixture("team"); + ownerId = fixture.userId; + teamCtx = makeUserCtx(fixture.workspace, { userId: ownerId }); +}); + +describe("import stamps the importing user", () => { + test("status reports, their updates and maintenances", async () => { + await withTestTransaction(async (tx) => { + const ctx = { ...teamCtx, db: tx }; + const pageRow = await tx + .insert(page) + .values({ + workspaceId: ctx.workspace.id, + title: `${TEST_PREFIX}-page`, + description: "", + slug: `${TEST_PREFIX}-${ctx.workspace.id}`, + customDomain: "", + }) + .returning() + .get(); + + const incidents: PhaseResult = { + phase: "incidents", + status: "completed", + resources: [ + { + sourceId: "inc_1", + name: `${TEST_PREFIX}-incident`, + status: "created", + data: { + report: { + title: `${TEST_PREFIX}-report`, + status: "resolved" as const, + workspaceId: ctx.workspace.id, + pageId: pageRow.id, + }, + updates: [ + { + status: "resolved" as const, + message: "done", + date: new Date("2024-01-01T00:00:00Z"), + }, + ], + sourceComponentIds: [], + }, + }, + ], + }; + await writeIncidentsPhase( + { ctx, tx, provider: "statuspage" }, + incidents, + pageRow.id, + new Map(), + ); + expect(incidents.resources[0].status).toBe("created"); + const reportId = incidents.resources[0].openstatusId as number; + const report = await tx + .select() + .from(statusReport) + .where(eq(statusReport.id, reportId)) + .get(); + expect(report?.createdBy).toBe(ownerId); + expect(report?.updatedBy).toBe(ownerId); + const updates = await tx + .select() + .from(statusReportUpdate) + .where(eq(statusReportUpdate.statusReportId, reportId)) + .all(); + expect(updates.map((u) => [u.createdBy, u.updatedBy])).toEqual([ + [ownerId, ownerId], + ]); + + const maintenances: PhaseResult = { + phase: "maintenances", + status: "completed", + resources: [ + { + sourceId: "mnt_1", + name: `${TEST_PREFIX}-maintenance`, + status: "created", + data: { + title: `${TEST_PREFIX}-maintenance`, + message: "planned", + from: new Date("2024-02-01T00:00:00Z"), + to: new Date("2024-02-01T01:00:00Z"), + workspaceId: ctx.workspace.id, + pageId: pageRow.id, + sourceComponentIds: [], + }, + }, + ], + }; + await writeMaintenancesPhase( + { ctx, tx, provider: "statuspage" }, + maintenances, + pageRow.id, + new Map(), + ); + expect(maintenances.resources[0].status).toBe("created"); + const row = await tx + .select() + .from(maintenance) + .where( + eq(maintenance.id, maintenances.resources[0].openstatusId as number), + ) + .get(); + expect(row?.createdBy).toBe(ownerId); + expect(row?.updatedBy).toBe(ownerId); + }); + }); +}); diff --git a/packages/services/src/import/phase-writers.ts b/packages/services/src/import/phase-writers.ts index 5a33ec01b..8122d196c 100644 --- a/packages/services/src/import/phase-writers.ts +++ b/packages/services/src/import/phase-writers.ts @@ -18,7 +18,7 @@ import type { PageComponentImpact } from "@openstatus/db/src/schema"; import type { PhaseResult, UpdateComponentImpact } from "@openstatus/importers"; import { emitAudit } from "../audit"; -import type { DB, ServiceContext } from "../context"; +import { type DB, type ServiceContext, tryGetActorUserId } from "../context"; import { withComponentImpacts, withPageComponentIds, @@ -357,6 +357,7 @@ export async function writeIncidentsPhase( ): Promise { const { ctx, tx } = pc; const workspaceId = ctx.workspace.id; + const actorUserId = tryGetActorUserId(ctx.actor); for (const resource of phase.resources) { try { @@ -427,6 +428,8 @@ export async function writeIncidentsPhase( status: data.report.status, workspaceId, pageId, + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning(); @@ -482,6 +485,8 @@ export async function writeIncidentsPhase( message: u.message, date: u.date, statusReportId: insertedReport.id, + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning() .get(); @@ -553,6 +558,7 @@ export async function writeMaintenancesPhase( ): Promise { const { ctx, tx } = pc; const workspaceId = ctx.workspace.id; + const actorUserId = tryGetActorUserId(ctx.actor); for (const resource of phase.resources) { try { @@ -619,6 +625,8 @@ export async function writeMaintenancesPhase( to: data.to, workspaceId, pageId, + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning(); diff --git a/packages/services/src/incident/index.ts b/packages/services/src/incident/index.ts index 007941e63..7130a9837 100644 --- a/packages/services/src/incident/index.ts +++ b/packages/services/src/incident/index.ts @@ -2,7 +2,8 @@ export { addIncidentNote } from "./add-note"; export { closeIncident, closeIncidentInTx } from "./close"; export { declareIncident } from "./declare"; export { deleteIncident, isDeletable } from "./delete"; -export { allowedTransitions, displayName } from "./internal"; +export { displayName } from "../attribution"; +export { allowedTransitions } from "./internal"; export { linkIncidentStatusReport, unlinkIncidentFromStatusReport, diff --git a/packages/services/src/incident/internal.ts b/packages/services/src/incident/internal.ts index 47f4aa33f..501168d64 100644 --- a/packages/services/src/incident/internal.ts +++ b/packages/services/src/incident/internal.ts @@ -9,6 +9,7 @@ import { user, } from "@openstatus/db/src/schema"; +import { displayName } from "../attribution"; import { emitAudit } from "../audit"; import { type DB, type ServiceContext, tryGetActorUserId } from "../context"; import { ConflictError, NotFoundError, ValidationError } from "../errors"; @@ -132,13 +133,3 @@ export async function userDisplayName( if (!row) return null; return displayName(row); } - -export function displayName(row: { - name: string | null; - firstName: string | null; - lastName: string | null; - email: string | null; -}): string { - const full = [row.firstName, row.lastName].filter(Boolean).join(" "); - return row.name || full || row.email || "Unknown user"; -} diff --git a/packages/services/src/incident/list-events.ts b/packages/services/src/incident/list-events.ts index 2ce56a56d..97f4a21bf 100644 --- a/packages/services/src/incident/list-events.ts +++ b/packages/services/src/incident/list-events.ts @@ -28,6 +28,7 @@ export async function listIncidentEvents(args: { lastName: true, email: true, photoUrl: true, + deletedAt: true, }, }, }, diff --git a/packages/services/src/incident/list.ts b/packages/services/src/incident/list.ts index af5a948c6..1160038e8 100644 --- a/packages/services/src/incident/list.ts +++ b/packages/services/src/incident/list.ts @@ -13,6 +13,7 @@ const userColumns = { lastName: true, email: true, photoUrl: true, + deletedAt: true, } as const; const statusOrder = sql`case ${incident.status} when 'open' then 0 when 'mitigated' then 1 when 'resolved' then 2 else 3 end`; diff --git a/packages/services/src/incident/postmortem-draft.ts b/packages/services/src/incident/postmortem-draft.ts index 45edd0aff..0afd874e4 100644 --- a/packages/services/src/incident/postmortem-draft.ts +++ b/packages/services/src/incident/postmortem-draft.ts @@ -4,10 +4,10 @@ import { statusReportUpdate, } from "@openstatus/db/src/schema"; +import { displayName } from "../attribution"; import { type ServiceContext, getReadDb } from "../context"; import { ConflictError, ForbiddenError, NotFoundError } from "../errors"; import { getSlackConnection } from "../integration/slack-connection"; -import { displayName } from "./internal"; import { getIncident } from "./list"; import { listIncidentEvents } from "./list-events"; import { draftPostmortem, getPostmortem } from "./postmortem"; diff --git a/packages/services/src/maintenance/__tests__/attribution.test.ts b/packages/services/src/maintenance/__tests__/attribution.test.ts new file mode 100644 index 000000000..4fa6840b3 --- /dev/null +++ b/packages/services/src/maintenance/__tests__/attribution.test.ts @@ -0,0 +1,120 @@ +import { db, eq } from "@openstatus/db"; +import { maintenance, page } from "@openstatus/db/src/schema"; +import { createUser } from "@openstatus/db/src/test/factories"; +import { expect } from "@std/expect"; +import { beforeAll, describe, test } from "@std/testing/bdd"; + +import { + createWorkspaceFixture, + makeApiKeyCtx, + makeSystemCtx, + makeUserCtx, + withTestTransaction, +} from "../../../test/helpers"; +import type { ServiceContext } from "../../context"; +import { createMaintenance } from "../create"; +import { getMaintenance } from "../list"; +import { updateMaintenance } from "../update"; + +const TEST_PREFIX = "svc-maintenance-attribution"; + +let teamCtx: ServiceContext; +let ownerId: number; +let pageId: number; + +beforeAll(async () => { + const fixture = await createWorkspaceFixture("team"); + ownerId = fixture.userId; + teamCtx = makeUserCtx(fixture.workspace, { userId: ownerId }); + const row = await db + .insert(page) + .values({ + workspaceId: fixture.workspace.id, + title: `${TEST_PREFIX}-page`, + description: "", + slug: `${TEST_PREFIX}-${fixture.workspace.id}`, + customDomain: "", + }) + .returning() + .get(); + pageId = row.id; +}); + +function range() { + const from = new Date(Date.now() + 60 * 60 * 1000); + return { from, to: new Date(from.getTime() + 60 * 60 * 1000) }; +} + +async function create(ctx: ServiceContext, title: string) { + return createMaintenance({ + ctx, + input: { + title: `${TEST_PREFIX}-${title}`, + message: "planned", + ...range(), + pageId, + pageComponentIds: [], + }, + }); +} + +describe("maintenance attribution", () => { + test("user actor stamps both columns and reads back", async () => { + await withTestTransaction(async (tx) => { + const ctx = { ...teamCtx, db: tx }; + const record = await create(ctx, "user"); + expect(record.createdBy).toBe(ownerId); + expect(record.updatedBy).toBe(ownerId); + + const full = await getMaintenance({ ctx, input: { id: record.id } }); + expect(full.createdByUser).toEqual({ id: ownerId, name: "Test User" }); + expect(full.updatedByUser).toEqual({ id: ownerId, name: "Test User" }); + }); + }); + + test("api key without a user and system actor leave NULL", async () => { + await withTestTransaction(async (tx) => { + const anonKey = { + ...makeApiKeyCtx(teamCtx.workspace, { keyId: "k", scopes: ["write"] }), + db: tx, + }; + const viaKey = await create(anonKey, "key"); + expect(viaKey.createdBy).toBeNull(); + expect(viaKey.updatedBy).toBeNull(); + + const system = { + ...makeSystemCtx(teamCtx.workspace, { job: "test" }), + db: tx, + }; + const viaSystem = await create(system, "system"); + expect(viaSystem.createdBy).toBeNull(); + + const full = await getMaintenance({ + ctx: anonKey, + input: { id: viaKey.id }, + }); + expect(full.createdByUser).toBeNull(); + }); + }); + + test("update moves updated_by and keeps created_by", async () => { + await withTestTransaction(async (tx) => { + const editor = await createUser({}, tx); + const record = await create({ ...teamCtx, db: tx }, "edit"); + await updateMaintenance({ + ctx: { + ...makeUserCtx(teamCtx.workspace, { userId: editor.id }), + db: tx, + }, + input: { id: record.id, title: `${TEST_PREFIX}-renamed` }, + }); + const row = await tx + .select() + .from(maintenance) + .where(eq(maintenance.id, record.id)) + .get(); + expect(row?.createdBy).toBe(ownerId); + expect(row?.updatedBy).toBe(editor.id); + }); + }); +}); diff --git a/packages/services/src/maintenance/create.ts b/packages/services/src/maintenance/create.ts index 02ebdcc2f..dc9b3294b 100644 --- a/packages/services/src/maintenance/create.ts +++ b/packages/services/src/maintenance/create.ts @@ -2,7 +2,11 @@ import { maintenance } from "@openstatus/db/src/schema"; import { emitAudit } from "../audit"; import { requireScope } from "../auth"; -import { type ServiceContext, withTransaction } from "../context"; +import { + type ServiceContext, + tryGetActorUserId, + withTransaction, +} from "../context"; import { ConflictError } from "../errors"; import type { Maintenance } from "../types"; import { @@ -19,6 +23,7 @@ export async function createMaintenance(args: { const { ctx } = args; requireScope(ctx, "write"); const input = CreateMaintenanceInput.parse(args.input); + const actorUserId = tryGetActorUserId(ctx.actor); return withTransaction(ctx, async (tx) => { await assertPageInWorkspace({ @@ -48,6 +53,8 @@ export async function createMaintenance(args: { message: input.message, from: input.from, to: input.to, + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning() .get(); diff --git a/packages/services/src/maintenance/list.ts b/packages/services/src/maintenance/list.ts index 5f98a34f8..70780d8e8 100644 --- a/packages/services/src/maintenance/list.ts +++ b/packages/services/src/maintenance/list.ts @@ -16,6 +16,7 @@ import { selectPageComponentSchema, } from "@openstatus/db/src/schema"; +import { type AttributedUser, loadAttributedUsers } from "../attribution"; import type { DB, ServiceContext } from "../context"; import type { Maintenance, PageComponent } from "../types"; import { getMaintenanceInWorkspace } from "./internal"; @@ -41,6 +42,8 @@ function periodToSince(period: MaintenanceListPeriod): Date { export type MaintenanceWithRelations = Maintenance & { pageComponents: PageComponent[]; pageComponentIds: number[]; + createdByUser: AttributedUser | null; + updatedByUser: AttributedUser | null; }; export type ListMaintenancesResult = { @@ -77,6 +80,11 @@ async function enrichMaintenancesBatch( .where(inArray(maintenancesToPageComponents.maintenanceId, ids)) .all(); + const users = await loadAttributedUsers( + db, + rows.flatMap((r) => [r.createdBy, r.updatedBy]), + ); + const componentsByMaintenance = new Map(); for (const row of assocRows) { const component = selectPageComponentSchema.parse(row.component); @@ -91,6 +99,10 @@ async function enrichMaintenancesBatch( ...r, pageComponents: components, pageComponentIds: components.map((c) => c.id), + createdByUser: + r.createdBy != null ? (users.get(r.createdBy) ?? null) : null, + updatedByUser: + r.updatedBy != null ? (users.get(r.updatedBy) ?? null) : null, }; }); } diff --git a/packages/services/src/maintenance/update.ts b/packages/services/src/maintenance/update.ts index 95c72f357..34b81aede 100644 --- a/packages/services/src/maintenance/update.ts +++ b/packages/services/src/maintenance/update.ts @@ -3,7 +3,11 @@ import { maintenance } from "@openstatus/db/src/schema"; import { emitAudit } from "../audit"; import { requireScope } from "../auth"; -import { type ServiceContext, withTransaction } from "../context"; +import { + type ServiceContext, + tryGetActorUserId, + withTransaction, +} from "../context"; import { ConflictError, InternalServiceError } from "../errors"; import type { Maintenance } from "../types"; import { @@ -36,7 +40,10 @@ export async function updateMaintenance(args: { throw new ConflictError("End date must be after start date."); } - const updateValues: Record = { updatedAt: new Date() }; + const updateValues: Record = { + updatedAt: new Date(), + updatedBy: tryGetActorUserId(ctx.actor), + }; if (input.title !== undefined) updateValues.title = input.title; if (input.message !== undefined) updateValues.message = input.message; if (input.from !== undefined) updateValues.from = input.from; diff --git a/packages/services/src/status-report/__tests__/attribution.test.ts b/packages/services/src/status-report/__tests__/attribution.test.ts new file mode 100644 index 000000000..ac17ef498 --- /dev/null +++ b/packages/services/src/status-report/__tests__/attribution.test.ts @@ -0,0 +1,252 @@ +import { db, eq } from "@openstatus/db"; +import { + page, + statusReport, + statusReportUpdate, +} from "@openstatus/db/src/schema"; +import { createUser } from "@openstatus/db/src/test/factories"; +import { expect } from "@std/expect"; +import { beforeAll, describe, test } from "@std/testing/bdd"; + +import { + createWorkspaceFixture, + makeApiKeyCtx, + makeSystemCtx, + makeUserCtx, + withTestTransaction, +} from "../../../test/helpers"; +import type { DB, ServiceContext } from "../../context"; +import { addStatusReportUpdate } from "../add-update"; +import { createStatusReport } from "../create"; +import { deleteStatusReportUpdate } from "../delete"; +import { getStatusReport } from "../list"; +import { updateStatusReport, updateStatusReportUpdate } from "../update"; + +const TEST_PREFIX = "svc-status-report-attribution"; + +let teamCtx: ServiceContext; +let ownerId: number; +let pageId: number; + +beforeAll(async () => { + const fixture = await createWorkspaceFixture("team"); + ownerId = fixture.userId; + teamCtx = makeUserCtx(fixture.workspace, { userId: ownerId }); + const row = await db + .insert(page) + .values({ + workspaceId: fixture.workspace.id, + title: `${TEST_PREFIX}-page`, + description: "", + slug: `${TEST_PREFIX}-${fixture.workspace.id}`, + customDomain: "", + }) + .returning() + .get(); + pageId = row.id; +}); + +async function create(ctx: ServiceContext, title: string) { + return createStatusReport({ + ctx, + input: { + title: `${TEST_PREFIX}-${title}`, + status: "investigating", + message: "m", + date: new Date(), + pageId, + pageComponentIds: [], + }, + }); +} + +async function readReport(tx: DB, id: number) { + const row = await tx + .select() + .from(statusReport) + .where(eq(statusReport.id, id)) + .get(); + if (!row) throw new Error(`report ${id} missing`); + return row; +} + +async function readUpdate(tx: DB, id: number) { + const row = await tx + .select() + .from(statusReportUpdate) + .where(eq(statusReportUpdate.id, id)) + .get(); + if (!row) throw new Error(`update ${id} missing`); + return row; +} + +describe("status report attribution", () => { + test("user actor stamps report and initial update", async () => { + await withTestTransaction(async (tx) => { + const ctx = { ...teamCtx, db: tx }; + const { statusReport: report, initialUpdate } = await create(ctx, "user"); + + expect(report.createdBy).toBe(ownerId); + expect(report.updatedBy).toBe(ownerId); + expect(initialUpdate.createdBy).toBe(ownerId); + expect(initialUpdate.updatedBy).toBe(ownerId); + + const full = await getStatusReport({ ctx, input: { id: report.id } }); + expect(full.createdByUser).toEqual({ id: ownerId, name: "Test User" }); + expect(full.updatedByUser).toEqual({ id: ownerId, name: "Test User" }); + expect(full.updates[0].createdByUser).toEqual({ + id: ownerId, + name: "Test User", + }); + }); + }); + + test("api key with a user stamps; without a user leaves NULL", async () => { + await withTestTransaction(async (tx) => { + const withUser = { + ...makeApiKeyCtx(teamCtx.workspace, { + keyId: "k-user", + userId: ownerId, + scopes: ["write"], + }), + db: tx, + }; + const { statusReport: stamped } = await create(withUser, "key-user"); + expect(stamped.createdBy).toBe(ownerId); + + const withoutUser = { + ...makeApiKeyCtx(teamCtx.workspace, { + keyId: "k-anon", + scopes: ["write"], + }), + db: tx, + }; + const { statusReport: anonymous, initialUpdate } = await create( + withoutUser, + "key-anon", + ); + expect(anonymous.createdBy).toBeNull(); + expect(anonymous.updatedBy).toBeNull(); + expect(initialUpdate.createdBy).toBeNull(); + + const full = await getStatusReport({ + ctx: withoutUser, + input: { id: anonymous.id }, + }); + expect(full.createdByUser).toBeNull(); + expect(full.updatedByUser).toBeNull(); + }); + }); + + test("system actor leaves NULL", async () => { + await withTestTransaction(async (tx) => { + const ctx = { + ...makeSystemCtx(teamCtx.workspace, { job: "test" }), + db: tx, + }; + const { statusReport: report, initialUpdate } = await create( + ctx, + "system", + ); + expect(report.createdBy).toBeNull(); + expect(initialUpdate.createdBy).toBeNull(); + }); + }); + + test("edits move updated_by but keep created_by", async () => { + await withTestTransaction(async (tx) => { + const editor = await createUser({}, tx); + const creatorCtx = { ...teamCtx, db: tx }; + const editorCtx = { + ...makeUserCtx(teamCtx.workspace, { userId: editor.id }), + db: tx, + }; + const { statusReport: report, initialUpdate } = await create( + creatorCtx, + "edits", + ); + + // adding an update stamps the update and touches the report + const { statusReportUpdate: added } = await addStatusReportUpdate({ + ctx: editorCtx, + input: { + statusReportId: report.id, + status: "identified", + message: "found it", + }, + }); + expect(added.createdBy).toBe(editor.id); + expect(added.updatedBy).toBe(editor.id); + let current = await readReport(tx, report.id); + expect(current.createdBy).toBe(ownerId); + expect(current.updatedBy).toBe(editor.id); + + // editing the report's metadata + await updateStatusReport({ + ctx: creatorCtx, + input: { id: report.id, title: `${TEST_PREFIX}-renamed` }, + }); + current = await readReport(tx, report.id); + expect(current.updatedBy).toBe(ownerId); + + // editing an update row + await updateStatusReportUpdate({ + ctx: editorCtx, + input: { id: initialUpdate.id, message: "reworded" }, + }); + const edited = await readUpdate(tx, initialUpdate.id); + expect(edited.createdBy).toBe(ownerId); + expect(edited.updatedBy).toBe(editor.id); + + // deleting the latest update is an edit of the report + await deleteStatusReportUpdate({ + ctx: editorCtx, + input: { id: added.id }, + }); + current = await readReport(tx, report.id); + expect(current.status).toBe("investigating"); + expect(current.updatedBy).toBe(editor.id); + + // same even when the status does not change + const { statusReportUpdate: repeated } = await addStatusReportUpdate({ + ctx: creatorCtx, + input: { + statusReportId: report.id, + status: "investigating", + message: "still looking", + }, + }); + current = await readReport(tx, report.id); + expect(current.updatedBy).toBe(ownerId); + await deleteStatusReportUpdate({ + ctx: editorCtx, + input: { id: repeated.id }, + }); + current = await readReport(tx, report.id); + expect(current.status).toBe("investigating"); + expect(current.updatedBy).toBe(editor.id); + }); + }); + + test("soft-deleted author reads as 'Deleted user'", async () => { + await withTestTransaction(async (tx) => { + const gone = await createUser( + { + name: "", + firstName: "", + lastName: "", + email: "", + deletedAt: new Date(), + }, + tx, + ); + const ctx = { + ...makeUserCtx(teamCtx.workspace, { userId: gone.id }), + db: tx, + }; + const { statusReport: report } = await create(ctx, "deleted"); + const full = await getStatusReport({ ctx, input: { id: report.id } }); + expect(full.createdByUser).toEqual({ id: gone.id, name: "Deleted user" }); + }); + }); +}); diff --git a/packages/services/src/status-report/add-update.ts b/packages/services/src/status-report/add-update.ts index 1c008fe05..db7b586c8 100644 --- a/packages/services/src/status-report/add-update.ts +++ b/packages/services/src/status-report/add-update.ts @@ -5,7 +5,11 @@ import { import { emitAudit } from "../audit"; import { requireScope } from "../auth"; -import { type ServiceContext, withTransaction } from "../context"; +import { + type ServiceContext, + tryGetActorUserId, + withTransaction, +} from "../context"; import { ConflictError, InternalServiceError } from "../errors"; import type { StatusReport, StatusReportUpdate } from "../types"; import { recomputeReportStatus } from "./derive-status"; @@ -30,6 +34,7 @@ export async function addStatusReportUpdate(args: { const { ctx } = args; requireScope(ctx, "write"); const input = AddStatusReportUpdateInput.parse(args.input); + const actorUserId = tryGetActorUserId(ctx.actor); return withTransaction(ctx, async (tx) => { const report = await getReportInWorkspace({ @@ -95,6 +100,8 @@ export async function addStatusReportUpdate(args: { status: input.status, date, message: input.message, + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning() .get(); @@ -108,6 +115,7 @@ export async function addStatusReportUpdate(args: { // derived, not assigned — a back-dated update must not win const updatedReport = await recomputeReportStatus(tx, report.id, { touchIfLatestIs: newUpdate.id, + updatedBy: actorUserId, }); if (!updatedReport) { diff --git a/packages/services/src/status-report/create.ts b/packages/services/src/status-report/create.ts index f185778a1..cb8ef979c 100644 --- a/packages/services/src/status-report/create.ts +++ b/packages/services/src/status-report/create.ts @@ -7,7 +7,11 @@ import { import { emitAudit } from "../audit"; import { requireScope } from "../auth"; -import { type ServiceContext, withTransaction } from "../context"; +import { + type ServiceContext, + tryGetActorUserId, + withTransaction, +} from "../context"; import { ConflictError, NotFoundError } from "../errors"; import { linkIncidentStatusReport } from "../incident/link-status-report"; import type { StatusReport, StatusReportUpdate } from "../types"; @@ -32,6 +36,7 @@ export async function createStatusReport(args: { const { ctx } = args; requireScope(ctx, "write"); const input = CreateStatusReportInput.parse(args.input); + const actorUserId = tryGetActorUserId(ctx.actor); return withTransaction(ctx, async (tx) => { const page_ = await tx @@ -67,6 +72,8 @@ export async function createStatusReport(args: { pageId: input.pageId, title: input.title, status: input.status, + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning() .get(); @@ -84,6 +91,8 @@ export async function createStatusReport(args: { status: input.status, date: input.date, message: input.message, + createdBy: actorUserId, + updatedBy: actorUserId, }) .returning() .get(); diff --git a/packages/services/src/status-report/delete.ts b/packages/services/src/status-report/delete.ts index 0ff50ed77..ed0bc2be9 100644 --- a/packages/services/src/status-report/delete.ts +++ b/packages/services/src/status-report/delete.ts @@ -3,7 +3,11 @@ import { statusReport, statusReportUpdate } from "@openstatus/db/src/schema"; import { emitAudit } from "../audit"; import { requireScope } from "../auth"; -import { type ServiceContext, withTransaction } from "../context"; +import { + type ServiceContext, + tryGetActorUserId, + withTransaction, +} from "../context"; import { unlinkIncidentFromStatusReport } from "../incident/link-status-report"; import { recomputeReportStatus } from "./derive-status"; import { getReportInWorkspace, getReportUpdateInWorkspace } from "./internal"; @@ -68,7 +72,10 @@ export async function deleteStatusReportUpdate(args: { .where(eq(statusReportUpdate.id, existing.id)); // deleting the latest update hands the status back to the one before it - await recomputeReportStatus(tx, existing.statusReportId); + await recomputeReportStatus(tx, existing.statusReportId, { + updatedBy: tryGetActorUserId(ctx.actor), + removed: existing, + }); await emitAudit(tx, ctx, { action: "status_report_update.delete", diff --git a/packages/services/src/status-report/derive-status.ts b/packages/services/src/status-report/derive-status.ts index 7462c082b..b6ecb46ae 100644 --- a/packages/services/src/status-report/derive-status.ts +++ b/packages/services/src/status-report/derive-status.ts @@ -49,6 +49,10 @@ export async function recomputeReportStatus( options?: { /** Bump `updatedAt` even if the status is unchanged, when this update is the latest one. */ touchIfLatestIs?: number; + /** Stamped on the report whenever the row is written. */ + updatedBy?: number | null; + /** The update just deleted; forces the write when it was the latest one. */ + removed?: DerivableUpdate; }, ): Promise { const rows = await tx @@ -75,15 +79,23 @@ export async function recomputeReportStatus( const isNewLatest = options?.touchIfLatestIs !== undefined && options.touchIfLatestIs === latest.id; + const removedLatest = + options?.removed !== undefined && + findLatestUpdate([...rows, options.removed])?.id === options.removed.id; // no-op edits (message, impacts) must not bump updatedAt — the RSS/Atom - // feed dates and sorts items by it. A new latest update always counts, - // even when it repeats the current status. - if (current.status === latest.status && !isNewLatest) return current; + // feed dates and sorts items by it. Adding or removing the latest update + // always counts, even when the status it leaves behind is the same. + if (current.status === latest.status && !isNewLatest && !removedLatest) + return current; return tx .update(statusReport) - .set({ status: latest.status, updatedAt: new Date() }) + .set({ + status: latest.status, + updatedAt: new Date(), + ...(options?.updatedBy !== undefined && { updatedBy: options.updatedBy }), + }) .where(eq(statusReport.id, statusReportId)) .returning() .get(); diff --git a/packages/services/src/status-report/list.ts b/packages/services/src/status-report/list.ts index 62256b93b..83a290318 100644 --- a/packages/services/src/status-report/list.ts +++ b/packages/services/src/status-report/list.ts @@ -21,6 +21,7 @@ import { statusReportUpdateToPageComponents, } from "@openstatus/db/src/schema"; +import { type AttributedUser, loadAttributedUsers } from "../attribution"; import { batchReads, type DB, type ServiceContext } from "../context"; import type { Page, @@ -48,22 +49,32 @@ function periodToSince(period: StatusReportListPeriod): Date { } } -export type StatusReportUpdateWithImpacts = StatusReportUpdate & { - /** Impacts this update set; empty for legacy reports. */ - componentImpacts: { pageComponentId: number; impact: PageComponentImpact }[]; +type Attributed = { + createdByUser: AttributedUser | null; + updatedByUser: AttributedUser | null; }; -export type StatusReportWithRelations = StatusReport & { - updates: StatusReportUpdateWithImpacts[]; - pageComponents: PageComponent[]; - /** Flat list of associated component ids. Convenience for proto conversion. */ - pageComponentIds: number[]; - /** - * The owning page with its full component roster. `null` when the report - * has no `pageId` — rare today but schema-allowed. - */ - page: (Page & { pageComponents: PageComponent[] }) | null; -}; +export type StatusReportUpdateWithImpacts = StatusReportUpdate & + Attributed & { + /** Impacts this update set; empty for legacy reports. */ + componentImpacts: { + pageComponentId: number; + impact: PageComponentImpact; + }[]; + }; + +export type StatusReportWithRelations = StatusReport & + Attributed & { + updates: StatusReportUpdateWithImpacts[]; + pageComponents: PageComponent[]; + /** Flat list of associated component ids. Convenience for proto conversion. */ + pageComponentIds: number[]; + /** + * The owning page with its full component roster. `null` when the report + * has no `pageId` — rare today but schema-allowed. + */ + page: (Page & { pageComponents: PageComponent[] }) | null; + }; export type ListStatusReportsResult = { items: StatusReportWithRelations[]; @@ -71,7 +82,7 @@ export type ListStatusReportsResult = { }; /** - * Load relations for a set of status reports in two round-trips regardless + * Load relations for a set of status reports in three round-trips regardless * of how many reports were passed in. Avoids the O(N) per-row pattern that * pairs badly with the dashboard's effectively-unlimited list request. */ @@ -153,10 +164,25 @@ async function enrichReportsBatch( else impactsByUpdate.set(row.statusReportUpdateId, [entry]); } + const users = await loadAttributedUsers(db, [ + ...rows.flatMap((r) => [r.createdBy, r.updatedBy]), + ...allUpdates.flatMap((u) => [u.createdBy, u.updatedBy]), + ]); + const attributed = (row: { + createdBy: number | null; + updatedBy: number | null; + }): Attributed => ({ + createdByUser: + row.createdBy != null ? (users.get(row.createdBy) ?? null) : null, + updatedByUser: + row.updatedBy != null ? (users.get(row.updatedBy) ?? null) : null, + }); + const updatesByReport = new Map(); for (const u of allUpdates) { const withImpacts = { ...u, + ...attributed(u), componentImpacts: impactsByUpdate.get(u.id) ?? [], }; const arr = updatesByReport.get(u.statusReportId); @@ -195,6 +221,7 @@ async function enrichReportsBatch( const components = componentsByReport.get(r.id) ?? []; return { ...r, + ...attributed(r), updates: updatesByReport.get(r.id) ?? [], pageComponents: components, pageComponentIds: components.map((c) => c.id), diff --git a/packages/services/src/status-report/update.ts b/packages/services/src/status-report/update.ts index 94b5ac85e..d2f801151 100644 --- a/packages/services/src/status-report/update.ts +++ b/packages/services/src/status-report/update.ts @@ -8,7 +8,11 @@ import { import { emitAudit } from "../audit"; import { requireScope } from "../auth"; -import { type ServiceContext, withTransaction } from "../context"; +import { + type ServiceContext, + tryGetActorUserId, + withTransaction, +} from "../context"; import { ConflictError, InternalServiceError } from "../errors"; import type { StatusReport, StatusReportUpdate } from "../types"; import { recomputeReportStatus } from "./derive-status"; @@ -46,6 +50,7 @@ export async function updateStatusReport(args: { const { ctx } = args; requireScope(ctx, "write"); const input = UpdateStatusReportInput.parse(args.input); + const actorUserId = tryGetActorUserId(ctx.actor); return withTransaction(ctx, async (tx) => { const report = await getReportInWorkspace({ @@ -62,7 +67,10 @@ export async function updateStatusReport(args: { ); let afterComponentIds = beforeComponentIds; - const updateValues: Record = { updatedAt: new Date() }; + const updateValues: Record = { + updatedAt: new Date(), + updatedBy: actorUserId, + }; if (input.title !== undefined) updateValues.title = input.title; if (input.status !== undefined) { const latest = await tx @@ -140,6 +148,7 @@ export async function updateStatusReportUpdate(args: { const { ctx } = args; requireScope(ctx, "write"); const input = UpdateStatusReportUpdateInput.parse(args.input); + const actorUserId = tryGetActorUserId(ctx.actor); return withTransaction(ctx, async (tx) => { const existing = await getReportUpdateInWorkspace({ @@ -151,7 +160,10 @@ export async function updateStatusReportUpdate(args: { const beforeImpacts = await getComponentImpactsForUpdate(tx, existing.id); const afterImpacts = input.componentImpacts ?? beforeImpacts; - const updateValues: Record = { updatedAt: new Date() }; + const updateValues: Record = { + updatedAt: new Date(), + updatedBy: actorUserId, + }; if (input.status !== undefined) updateValues.status = input.status; if (input.message !== undefined) updateValues.message = input.message; if (input.date !== undefined) updateValues.date = input.date; @@ -223,7 +235,9 @@ export async function updateStatusReportUpdate(args: { } // editing status or date can change which update is latest - await recomputeReportStatus(tx, existing.statusReportId); + await recomputeReportStatus(tx, existing.statusReportId, { + updatedBy: actorUserId, + }); await emitAudit(tx, ctx, { action: "status_report_update.update", diff --git a/packages/services/src/status-timeline/__tests__/events.test.ts b/packages/services/src/status-timeline/__tests__/events.test.ts index afe245ae4..2fb8bf9ee 100644 --- a/packages/services/src/status-timeline/__tests__/events.test.ts +++ b/packages/services/src/status-timeline/__tests__/events.test.ts @@ -124,6 +124,8 @@ describe("getEvents lookback", () => { to, workspaceId: 1, pageId: 1, + createdBy: null, + updatedBy: null, createdAt: from, updatedAt: from, maintenancesToPageComponents: [], diff --git a/packages/tracker/src/tracker.test.ts b/packages/tracker/src/tracker.test.ts index 4046f8455..d2ace251a 100644 --- a/packages/tracker/src/tracker.test.ts +++ b/packages/tracker/src/tracker.test.ts @@ -57,6 +57,8 @@ function createMaintenance(overrides: Partial = {}): Maintenance { to: new Date("2024-01-01T01:00:00.000Z"), workspaceId: 1, pageId: null, + createdBy: null, + updatedBy: null, createdAt: new Date("2024-01-01T00:00:00.000Z"), updatedAt: new Date("2024-01-01T00:00:00.000Z"), ...overrides, @@ -72,6 +74,8 @@ function createStatusReportUpdate( date: new Date("2024-01-01T00:00:00.000Z"), message: "Looking into it", statusReportId: 1, + createdBy: null, + updatedBy: null, createdAt: new Date("2024-01-01T00:00:00.000Z"), updatedAt: new Date("2024-01-01T00:00:00.000Z"), ...overrides, @@ -87,6 +91,8 @@ function createStatusReport( title: "Status report", workspaceId: 1, pageId: null, + createdBy: null, + updatedBy: null, createdAt: new Date("2024-01-01T00:00:00.000Z"), updatedAt: new Date("2024-01-01T00:00:00.000Z"), ...overrides, diff --git a/packages/tracker/src/tracker.ts b/packages/tracker/src/tracker.ts index e89ff8f31..2e801bece 100644 --- a/packages/tracker/src/tracker.ts +++ b/packages/tracker/src/tracker.ts @@ -16,11 +16,13 @@ export type Monitor = { ok: number; day: string; }; -type StatusReports = (StatusReport & { - statusReportUpdates?: StatusReportUpdate[]; +// public page payloads strip author columns; the tracker never reads them +type AuthorColumns = "createdBy" | "updatedBy"; +type StatusReports = (Omit & { + statusReportUpdates?: Omit[]; })[]; type Incidents = MonitorIncident[]; -type Maintenances = Maintenance[]; +type Maintenances = Omit[]; /** * Tracker Class is supposed to handle the data and calculate from a single monitor. @@ -38,7 +40,7 @@ export class Tracker { data?: Monitor[]; statusReports?: StatusReports; incidents?: Incidents; - maintenances?: Maintenance[]; + maintenances?: Maintenances; }) { this.data = arg.data || []; // TODO: use another Class to handle a single Day this.statusReports = arg.statusReports || []; -- 2.51.2