Openstatus www.openstatus.dev

feat: page components [migration] (#1743)

* feat: page components migration

* chore: sync data [to be reverted]

* fix: tsc and transaction

* fix: test location

* fix: missing description

authored by

Maximilian Kaske and committed by
GitHub
06d8d536 e04c855d

+5759 -28
+576
apps/server/src/routes/v1/__test__/sync.test.ts
··· 1 + import { afterAll, beforeAll, describe, expect, test } from "bun:test"; 2 + import { and, db, eq, inArray } from "@openstatus/db"; 3 + import { 4 + maintenance, 5 + maintenancesToMonitors, 6 + maintenancesToPageComponents, 7 + monitor, 8 + monitorsToPages, 9 + monitorsToStatusReport, 10 + page, 11 + pageComponent, 12 + statusReport, 13 + statusReportUpdate, 14 + statusReportsToPageComponents, 15 + } from "@openstatus/db/src/schema"; 16 + 17 + import { app } from "@/index"; 18 + 19 + /** 20 + * Sync Tests for REST API: Verify that mutations to legacy tables also sync to new page_component tables 21 + * 22 + * These tests use the REST API endpoints and verify that the sync operations 23 + * correctly update the new page_component related tables. 24 + */ 25 + 26 + const TEST_PREFIX = "api-sync-test"; 27 + let testPageId: number; 28 + let testMonitorId: number; 29 + 30 + beforeAll(async () => { 31 + // Clean up any existing test data 32 + await db 33 + .delete(pageComponent) 34 + .where(eq(pageComponent.name, `${TEST_PREFIX}-monitor`)); 35 + await db.delete(page).where(eq(page.slug, `${TEST_PREFIX}-page`)); 36 + await db.delete(monitor).where(eq(monitor.name, `${TEST_PREFIX}-monitor`)); 37 + 38 + // Create test page 39 + const testPage = await db 40 + .insert(page) 41 + .values({ 42 + workspaceId: 1, 43 + title: "API Sync Test Page", 44 + description: "A test page for API sync tests", 45 + slug: `${TEST_PREFIX}-page`, 46 + customDomain: "", 47 + }) 48 + .returning() 49 + .get(); 50 + testPageId = testPage.id; 51 + 52 + // Create test monitor 53 + const testMonitor = await db 54 + .insert(monitor) 55 + .values({ 56 + workspaceId: 1, 57 + name: `${TEST_PREFIX}-monitor`, 58 + url: "https://api-sync-test.example.com", 59 + periodicity: "1m", 60 + active: true, 61 + regions: "ams", 62 + jobType: "http", 63 + }) 64 + .returning() 65 + .get(); 66 + testMonitorId = testMonitor.id; 67 + 68 + // Add monitor to page (creates monitors_to_pages entry) 69 + await db.insert(monitorsToPages).values({ 70 + monitorId: testMonitorId, 71 + pageId: testPageId, 72 + }); 73 + 74 + // Create page_component for the monitor (simulating initial sync) 75 + await db 76 + .insert(pageComponent) 77 + .values({ 78 + workspaceId: 1, 79 + pageId: testPageId, 80 + monitorId: testMonitorId, 81 + type: "monitor", 82 + name: `${TEST_PREFIX}-monitor`, 83 + }) 84 + .onConflictDoNothing(); 85 + }); 86 + 87 + afterAll(async () => { 88 + // Clean up test data in correct order 89 + await db 90 + .delete(maintenancesToPageComponents) 91 + .where( 92 + inArray( 93 + maintenancesToPageComponents.pageComponentId, 94 + db 95 + .select({ id: pageComponent.id }) 96 + .from(pageComponent) 97 + .where(eq(pageComponent.pageId, testPageId)), 98 + ), 99 + ); 100 + await db 101 + .delete(statusReportsToPageComponents) 102 + .where( 103 + inArray( 104 + statusReportsToPageComponents.pageComponentId, 105 + db 106 + .select({ id: pageComponent.id }) 107 + .from(pageComponent) 108 + .where(eq(pageComponent.pageId, testPageId)), 109 + ), 110 + ); 111 + await db.delete(pageComponent).where(eq(pageComponent.pageId, testPageId)); 112 + await db 113 + .delete(monitorsToPages) 114 + .where(eq(monitorsToPages.pageId, testPageId)); 115 + await db 116 + .delete(maintenancesToMonitors) 117 + .where(eq(maintenancesToMonitors.monitorId, testMonitorId)); 118 + await db 119 + .delete(monitorsToStatusReport) 120 + .where(eq(monitorsToStatusReport.monitorId, testMonitorId)); 121 + await db 122 + .delete(statusReportUpdate) 123 + .where( 124 + inArray( 125 + statusReportUpdate.statusReportId, 126 + db 127 + .select({ id: statusReport.id }) 128 + .from(statusReport) 129 + .where(eq(statusReport.pageId, testPageId)), 130 + ), 131 + ); 132 + await db.delete(statusReport).where(eq(statusReport.pageId, testPageId)); 133 + await db.delete(maintenance).where(eq(maintenance.pageId, testPageId)); 134 + await db.delete(page).where(eq(page.slug, `${TEST_PREFIX}-page`)); 135 + await db.delete(monitor).where(eq(monitor.name, `${TEST_PREFIX}-monitor`)); 136 + }); 137 + 138 + describe("API Sync: POST /v1/maintenance - maintenance_to_page_component", () => { 139 + let createdMaintenanceId: number; 140 + 141 + afterAll(async () => { 142 + if (createdMaintenanceId) { 143 + await db 144 + .delete(maintenancesToPageComponents) 145 + .where( 146 + eq(maintenancesToPageComponents.maintenanceId, createdMaintenanceId), 147 + ); 148 + await db 149 + .delete(maintenancesToMonitors) 150 + .where(eq(maintenancesToMonitors.maintenanceId, createdMaintenanceId)); 151 + await db 152 + .delete(maintenance) 153 + .where(eq(maintenance.id, createdMaintenanceId)); 154 + } 155 + }); 156 + 157 + test("creating maintenance with monitors syncs to maintenance_to_page_component", async () => { 158 + const from = new Date(); 159 + const to = new Date(from.getTime() + 3600000); 160 + 161 + const res = await app.request("/v1/maintenance", { 162 + method: "POST", 163 + headers: { 164 + "x-openstatus-key": "1", 165 + "content-type": "application/json", 166 + }, 167 + body: JSON.stringify({ 168 + title: `${TEST_PREFIX} Maintenance`, 169 + message: "Test maintenance for sync", 170 + from: from.toISOString(), 171 + to: to.toISOString(), 172 + monitorIds: [testMonitorId], 173 + pageId: testPageId, 174 + }), 175 + }); 176 + 177 + expect(res.status).toBe(200); 178 + 179 + const data = await res.json(); 180 + createdMaintenanceId = data.id; 181 + 182 + // Verify maintenance_to_monitor was created 183 + const maintenanceToMonitor = 184 + await db.query.maintenancesToMonitors.findFirst({ 185 + where: and( 186 + eq(maintenancesToMonitors.maintenanceId, createdMaintenanceId), 187 + eq(maintenancesToMonitors.monitorId, testMonitorId), 188 + ), 189 + }); 190 + expect(maintenanceToMonitor).toBeDefined(); 191 + 192 + // Verify maintenance_to_page_component was synced 193 + const component = await db.query.pageComponent.findFirst({ 194 + where: and( 195 + eq(pageComponent.monitorId, testMonitorId), 196 + eq(pageComponent.pageId, testPageId), 197 + ), 198 + }); 199 + 200 + if (component) { 201 + const maintenanceToComponent = 202 + await db.query.maintenancesToPageComponents.findFirst({ 203 + where: and( 204 + eq( 205 + maintenancesToPageComponents.maintenanceId, 206 + createdMaintenanceId, 207 + ), 208 + eq(maintenancesToPageComponents.pageComponentId, component.id), 209 + ), 210 + }); 211 + expect(maintenanceToComponent).toBeDefined(); 212 + } 213 + }); 214 + }); 215 + 216 + describe("API Sync: PUT /v1/maintenance - maintenance_to_page_component updates", () => { 217 + let testMaintenanceId: number; 218 + 219 + beforeAll(async () => { 220 + // Create a maintenance for update tests 221 + const from = new Date(); 222 + const to = new Date(from.getTime() + 3600000); 223 + 224 + const newMaintenance = await db 225 + .insert(maintenance) 226 + .values({ 227 + workspaceId: 1, 228 + pageId: testPageId, 229 + title: `${TEST_PREFIX} Update Test Maintenance`, 230 + message: "For update tests", 231 + from, 232 + to, 233 + }) 234 + .returning() 235 + .get(); 236 + testMaintenanceId = newMaintenance.id; 237 + 238 + // Add monitor to maintenance 239 + await db.insert(maintenancesToMonitors).values({ 240 + maintenanceId: testMaintenanceId, 241 + monitorId: testMonitorId, 242 + }); 243 + 244 + // Sync to page component 245 + const component = await db.query.pageComponent.findFirst({ 246 + where: and( 247 + eq(pageComponent.monitorId, testMonitorId), 248 + eq(pageComponent.pageId, testPageId), 249 + ), 250 + }); 251 + 252 + if (component) { 253 + await db 254 + .insert(maintenancesToPageComponents) 255 + .values({ 256 + maintenanceId: testMaintenanceId, 257 + pageComponentId: component.id, 258 + }) 259 + .onConflictDoNothing(); 260 + } 261 + }); 262 + 263 + afterAll(async () => { 264 + if (testMaintenanceId) { 265 + await db 266 + .delete(maintenancesToPageComponents) 267 + .where( 268 + eq(maintenancesToPageComponents.maintenanceId, testMaintenanceId), 269 + ); 270 + await db 271 + .delete(maintenancesToMonitors) 272 + .where(eq(maintenancesToMonitors.maintenanceId, testMaintenanceId)); 273 + await db.delete(maintenance).where(eq(maintenance.id, testMaintenanceId)); 274 + } 275 + }); 276 + 277 + test("removing monitors from maintenance syncs delete to maintenance_to_page_component", async () => { 278 + const res = await app.request(`/v1/maintenance/${testMaintenanceId}`, { 279 + method: "PUT", 280 + headers: { 281 + "x-openstatus-key": "1", 282 + "content-type": "application/json", 283 + }, 284 + body: JSON.stringify({ 285 + monitorIds: [], 286 + }), 287 + }); 288 + 289 + expect(res.status).toBe(200); 290 + 291 + // Verify maintenance_to_monitor was deleted 292 + const maintenanceToMonitor = 293 + await db.query.maintenancesToMonitors.findFirst({ 294 + where: and( 295 + eq(maintenancesToMonitors.maintenanceId, testMaintenanceId), 296 + eq(maintenancesToMonitors.monitorId, testMonitorId), 297 + ), 298 + }); 299 + expect(maintenanceToMonitor).toBeUndefined(); 300 + 301 + // Verify maintenance_to_page_component was also deleted 302 + const maintenanceToComponent = 303 + await db.query.maintenancesToPageComponents.findFirst({ 304 + where: eq( 305 + maintenancesToPageComponents.maintenanceId, 306 + testMaintenanceId, 307 + ), 308 + }); 309 + expect(maintenanceToComponent).toBeUndefined(); 310 + }); 311 + }); 312 + 313 + describe("API Sync: POST /v1/status_report - status_report_to_page_component", () => { 314 + let createdStatusReportId: number; 315 + 316 + afterAll(async () => { 317 + if (createdStatusReportId) { 318 + await db 319 + .delete(statusReportsToPageComponents) 320 + .where( 321 + eq( 322 + statusReportsToPageComponents.statusReportId, 323 + createdStatusReportId, 324 + ), 325 + ); 326 + await db 327 + .delete(monitorsToStatusReport) 328 + .where( 329 + eq(monitorsToStatusReport.statusReportId, createdStatusReportId), 330 + ); 331 + await db 332 + .delete(statusReportUpdate) 333 + .where(eq(statusReportUpdate.statusReportId, createdStatusReportId)); 334 + await db 335 + .delete(statusReport) 336 + .where(eq(statusReport.id, createdStatusReportId)); 337 + } 338 + }); 339 + 340 + test("creating status report with monitors syncs to status_report_to_page_component", async () => { 341 + const res = await app.request("/v1/status_report", { 342 + method: "POST", 343 + headers: { 344 + "x-openstatus-key": "1", 345 + "content-type": "application/json", 346 + }, 347 + body: JSON.stringify({ 348 + title: `${TEST_PREFIX} Status Report`, 349 + status: "investigating", 350 + message: "Test status report for sync", 351 + monitorIds: [testMonitorId], 352 + pageId: testPageId, 353 + }), 354 + }); 355 + 356 + expect(res.status).toBe(200); 357 + 358 + const data = await res.json(); 359 + createdStatusReportId = data.id; 360 + 361 + // Verify status_report_to_monitors was created 362 + const reportToMonitor = await db.query.monitorsToStatusReport.findFirst({ 363 + where: and( 364 + eq(monitorsToStatusReport.statusReportId, createdStatusReportId), 365 + eq(monitorsToStatusReport.monitorId, testMonitorId), 366 + ), 367 + }); 368 + expect(reportToMonitor).toBeDefined(); 369 + 370 + // Verify status_report_to_page_component was synced 371 + const component = await db.query.pageComponent.findFirst({ 372 + where: and( 373 + eq(pageComponent.monitorId, testMonitorId), 374 + eq(pageComponent.pageId, testPageId), 375 + ), 376 + }); 377 + 378 + if (component) { 379 + const reportToComponent = 380 + await db.query.statusReportsToPageComponents.findFirst({ 381 + where: and( 382 + eq( 383 + statusReportsToPageComponents.statusReportId, 384 + createdStatusReportId, 385 + ), 386 + eq(statusReportsToPageComponents.pageComponentId, component.id), 387 + ), 388 + }); 389 + expect(reportToComponent).toBeDefined(); 390 + } 391 + }); 392 + }); 393 + 394 + describe("API Sync: POST /v1/page - monitors_to_pages -> page_component", () => { 395 + let createdPageId: number; 396 + 397 + afterAll(async () => { 398 + if (createdPageId) { 399 + await db 400 + .delete(pageComponent) 401 + .where(eq(pageComponent.pageId, createdPageId)); 402 + await db 403 + .delete(monitorsToPages) 404 + .where(eq(monitorsToPages.pageId, createdPageId)); 405 + await db.delete(page).where(eq(page.id, createdPageId)); 406 + } 407 + }); 408 + 409 + test("creating page with monitors syncs to page_component", async () => { 410 + const res = await app.request("/v1/page", { 411 + method: "POST", 412 + headers: { 413 + "x-openstatus-key": "1", 414 + "content-type": "application/json", 415 + }, 416 + body: JSON.stringify({ 417 + title: `${TEST_PREFIX} New Page`, 418 + description: "Test page for sync", 419 + slug: `${TEST_PREFIX}-new-page`, 420 + monitors: [testMonitorId], 421 + }), 422 + }); 423 + 424 + expect(res.status).toBe(200); 425 + 426 + const data = await res.json(); 427 + createdPageId = data.id; 428 + 429 + // Verify monitors_to_pages was created 430 + const monitorToPage = await db.query.monitorsToPages.findFirst({ 431 + where: and( 432 + eq(monitorsToPages.monitorId, testMonitorId), 433 + eq(monitorsToPages.pageId, createdPageId), 434 + ), 435 + }); 436 + expect(monitorToPage).toBeDefined(); 437 + 438 + // Verify page_component was synced 439 + const component = await db.query.pageComponent.findFirst({ 440 + where: and( 441 + eq(pageComponent.monitorId, testMonitorId), 442 + eq(pageComponent.pageId, createdPageId), 443 + ), 444 + }); 445 + expect(component).toBeDefined(); 446 + expect(component?.type).toBe("monitor"); 447 + }); 448 + }); 449 + 450 + describe("API Sync: PUT /v1/page - monitors_to_pages -> page_component updates", () => { 451 + let updateTestPageId: number; 452 + 453 + beforeAll(async () => { 454 + // Create a page for update tests 455 + const updateTestPage = await db 456 + .insert(page) 457 + .values({ 458 + workspaceId: 1, 459 + title: `${TEST_PREFIX} Update Test Page`, 460 + description: "For update tests", 461 + slug: `${TEST_PREFIX}-update-page`, 462 + customDomain: "", 463 + }) 464 + .returning() 465 + .get(); 466 + updateTestPageId = updateTestPage.id; 467 + }); 468 + 469 + afterAll(async () => { 470 + if (updateTestPageId) { 471 + await db 472 + .delete(pageComponent) 473 + .where(eq(pageComponent.pageId, updateTestPageId)); 474 + await db 475 + .delete(monitorsToPages) 476 + .where(eq(monitorsToPages.pageId, updateTestPageId)); 477 + await db.delete(page).where(eq(page.id, updateTestPageId)); 478 + } 479 + }); 480 + 481 + test("adding monitors to page syncs to page_component", async () => { 482 + const res = await app.request(`/v1/page/${updateTestPageId}`, { 483 + method: "PUT", 484 + headers: { 485 + "x-openstatus-key": "1", 486 + "content-type": "application/json", 487 + }, 488 + body: JSON.stringify({ 489 + monitors: [testMonitorId], 490 + }), 491 + }); 492 + 493 + expect(res.status).toBe(200); 494 + 495 + // Verify monitors_to_pages was created 496 + const monitorToPage = await db.query.monitorsToPages.findFirst({ 497 + where: and( 498 + eq(monitorsToPages.monitorId, testMonitorId), 499 + eq(monitorsToPages.pageId, updateTestPageId), 500 + ), 501 + }); 502 + expect(monitorToPage).toBeDefined(); 503 + 504 + // Verify page_component was synced 505 + const component = await db.query.pageComponent.findFirst({ 506 + where: and( 507 + eq(pageComponent.monitorId, testMonitorId), 508 + eq(pageComponent.pageId, updateTestPageId), 509 + ), 510 + }); 511 + expect(component).toBeDefined(); 512 + }); 513 + 514 + test("removing monitors from page syncs delete to page_component", async () => { 515 + // First ensure monitor is on the page 516 + await db 517 + .insert(monitorsToPages) 518 + .values({ 519 + monitorId: testMonitorId, 520 + pageId: updateTestPageId, 521 + }) 522 + .onConflictDoNothing(); 523 + 524 + await db 525 + .insert(pageComponent) 526 + .values({ 527 + workspaceId: 1, 528 + pageId: updateTestPageId, 529 + monitorId: testMonitorId, 530 + type: "monitor", 531 + name: `${TEST_PREFIX}-monitor`, 532 + }) 533 + .onConflictDoNothing(); 534 + 535 + // Verify page_component exists before removal 536 + let component = await db.query.pageComponent.findFirst({ 537 + where: and( 538 + eq(pageComponent.monitorId, testMonitorId), 539 + eq(pageComponent.pageId, updateTestPageId), 540 + ), 541 + }); 542 + expect(component).toBeDefined(); 543 + 544 + // Remove all monitors 545 + const res = await app.request(`/v1/page/${updateTestPageId}`, { 546 + method: "PUT", 547 + headers: { 548 + "x-openstatus-key": "1", 549 + "content-type": "application/json", 550 + }, 551 + body: JSON.stringify({ 552 + monitors: [], 553 + }), 554 + }); 555 + 556 + expect(res.status).toBe(200); 557 + 558 + // Verify monitors_to_pages was deleted 559 + const monitorToPage = await db.query.monitorsToPages.findFirst({ 560 + where: and( 561 + eq(monitorsToPages.monitorId, testMonitorId), 562 + eq(monitorsToPages.pageId, updateTestPageId), 563 + ), 564 + }); 565 + expect(monitorToPage).toBeUndefined(); 566 + 567 + // Verify page_component was deleted 568 + component = await db.query.pageComponent.findFirst({ 569 + where: and( 570 + eq(pageComponent.monitorId, testMonitorId), 571 + eq(pageComponent.pageId, updateTestPageId), 572 + ), 573 + }); 574 + expect(component).toBeUndefined(); 575 + }); 576 + });
+15 -1
apps/server/src/routes/v1/maintenances/post.ts
··· 3 3 import { trackMiddleware } from "@/libs/middlewares"; 4 4 import { createRoute } from "@hono/zod-openapi"; 5 5 import { Events } from "@openstatus/analytics"; 6 - import { and, db, eq, inArray, isNotNull, isNull } from "@openstatus/db"; 6 + import { 7 + and, 8 + db, 9 + eq, 10 + inArray, 11 + isNotNull, 12 + isNull, 13 + syncMaintenanceToMonitorInsertMany, 14 + } from "@openstatus/db"; 7 15 import { monitor, page, pageSubscriber } from "@openstatus/db/src/schema"; 8 16 import { 9 17 maintenance, ··· 110 118 })), 111 119 ) 112 120 .run(); 121 + // Sync to page components 122 + await syncMaintenanceToMonitorInsertMany( 123 + tx, 124 + newMaintenance.id, 125 + input.monitorIds, 126 + ); 113 127 } 114 128 115 129 return newMaintenance;
+13 -1
apps/server/src/routes/v1/maintenances/put.ts
··· 2 2 import { trackMiddleware } from "@/libs/middlewares"; 3 3 import { createRoute } from "@hono/zod-openapi"; 4 4 import { Events } from "@openstatus/analytics"; 5 - import { and, db, eq, inArray, isNull } from "@openstatus/db"; 5 + import { 6 + and, 7 + db, 8 + eq, 9 + inArray, 10 + isNull, 11 + syncMaintenanceToMonitorDeleteByMaintenance, 12 + syncMaintenanceToMonitorInsertMany, 13 + } from "@openstatus/db"; 6 14 import { monitor, page } from "@openstatus/db/src/schema"; 7 15 import { 8 16 maintenance, ··· 128 136 .delete(maintenancesToMonitors) 129 137 .where(eq(maintenancesToMonitors.maintenanceId, Number(id))) 130 138 .run(); 139 + // Sync delete to page components 140 + await syncMaintenanceToMonitorDeleteByMaintenance(tx, Number(id)); 131 141 132 142 // Add new monitor associations 133 143 if (monitorIds.length > 0) { ··· 140 150 })), 141 151 ) 142 152 .run(); 153 + // Sync to page components 154 + await syncMaintenanceToMonitorInsertMany(tx, Number(id), monitorIds); 143 155 } 144 156 } 145 157
+14 -1
apps/server/src/routes/v1/pages/post.ts
··· 1 1 import { createRoute } from "@hono/zod-openapi"; 2 2 3 - import { and, eq, inArray, isNull, sql } from "@openstatus/db"; 3 + import { 4 + and, 5 + eq, 6 + inArray, 7 + isNull, 8 + sql, 9 + syncMonitorsToPageInsert, 10 + } from "@openstatus/db"; 4 11 import { db } from "@openstatus/db/src/db"; 5 12 import { 6 13 monitor, ··· 182 189 .insert(monitorsToPages) 183 190 .values({ pageId: _page.id, ...values }) 184 191 .run(); 192 + // Sync to page components 193 + await syncMonitorsToPageInsert(db, { 194 + monitorId: values.monitorId, 195 + pageId: _page.id, 196 + order: "order" in values ? values.order : undefined, 197 + }); 185 198 } 186 199 } 187 200 const data = transformPageData(PageSchema.parse(_page));
+19 -1
apps/server/src/routes/v1/pages/put.ts
··· 3 3 import { OpenStatusApiError, openApiErrorResponses } from "@/libs/errors"; 4 4 import { trackMiddleware } from "@/libs/middlewares"; 5 5 import { Events } from "@openstatus/analytics"; 6 - import { and, eq, inArray, isNull, sql } from "@openstatus/db"; 6 + import { 7 + and, 8 + eq, 9 + inArray, 10 + isNull, 11 + sql, 12 + syncMonitorsToPageDelete, 13 + syncMonitorsToPageInsert, 14 + } from "@openstatus/db"; 7 15 import { db } from "@openstatus/db/src/db"; 8 16 import { 9 17 monitor, ··· 194 202 eq(monitorsToPages.pageId, newPage.id), 195 203 ), 196 204 ); 205 + // Sync delete to page components 206 + for (const monitorId of removedMonitors) { 207 + await syncMonitorsToPageDelete(db, { monitorId, pageId: newPage.id }); 208 + } 197 209 } 198 210 199 211 if (monitors) { ··· 208 220 target: [monitorsToPages.monitorId, monitorsToPages.pageId], 209 221 set: { order: sql.raw("excluded.`order`") }, 210 222 }); 223 + // Sync to page components (existing ones will be ignored due to onConflictDoNothing in sync) 224 + await syncMonitorsToPageInsert(db, { 225 + monitorId: values.monitorId, 226 + pageId: newPage.id, 227 + order: "order" in values ? values.order : undefined, 228 + }); 211 229 } 212 230 } 213 231
+15 -1
apps/server/src/routes/v1/statusReports/post.ts
··· 1 1 import { createRoute, z } from "@hono/zod-openapi"; 2 2 3 - import { and, db, eq, inArray, isNotNull, isNull } from "@openstatus/db"; 3 + import { 4 + and, 5 + db, 6 + eq, 7 + inArray, 8 + isNotNull, 9 + isNull, 10 + syncStatusReportToMonitorInsertMany, 11 + } from "@openstatus/db"; 4 12 import { 5 13 monitor, 6 14 monitorsToStatusReport, ··· 133 141 }), 134 142 ) 135 143 .returning(); 144 + // Sync to page components 145 + await syncStatusReportToMonitorInsertMany( 146 + db, 147 + _newStatusReport.id, 148 + input.monitorIds, 149 + ); 136 150 } 137 151 138 152 if (limits["status-subscribers"] && _newStatusReport.pageId) {
+16
packages/api/src/router/maintenance.ts
··· 9 9 gte, 10 10 inArray, 11 11 lte, 12 + syncMaintenanceToMonitorDeleteByMaintenance, 13 + syncMaintenanceToMonitorInsertMany, 12 14 } from "@openstatus/db"; 13 15 import { 14 16 maintenance, ··· 217 219 monitorId, 218 220 })), 219 221 ); 222 + // Sync to page components 223 + await syncMaintenanceToMonitorInsertMany( 224 + tx, 225 + newMaintenance.id, 226 + opts.input.monitors, 227 + ); 220 228 } 221 229 222 230 return newMaintenance; ··· 287 295 .delete(maintenancesToMonitors) 288 296 .where(eq(maintenancesToMonitors.maintenanceId, _maintenance.id)) 289 297 .run(); 298 + // Sync delete to page components 299 + await syncMaintenanceToMonitorDeleteByMaintenance(tx, _maintenance.id); 290 300 291 301 // Create new relations if monitors are provided 292 302 if (opts.input.monitors?.length) { ··· 295 305 maintenanceId: _maintenance.id, 296 306 monitorId, 297 307 })), 308 + ); 309 + // Sync to page components 310 + await syncMaintenanceToMonitorInsertMany( 311 + tx, 312 + _maintenance.id, 313 + opts.input.monitors, 298 314 ); 299 315 } 300 316
+48 -7
packages/api/src/router/monitor.ts
··· 14 14 statusAssertion, 15 15 textBodyAssertion, 16 16 } from "@openstatus/assertions"; 17 - import { type SQL, and, count, eq, inArray, isNull, sql } from "@openstatus/db"; 17 + import { 18 + type SQL, 19 + and, 20 + count, 21 + eq, 22 + inArray, 23 + isNull, 24 + sql, 25 + syncMaintenanceToMonitorDeleteByMonitors, 26 + syncMonitorsToPageDelete, 27 + syncMonitorsToPageDeleteByMonitors, 28 + syncMonitorsToPageInsertMany, 29 + syncStatusReportToMonitorDeleteByMonitors, 30 + } from "@openstatus/db"; 18 31 import { 19 32 insertMonitorSchema, 20 33 maintenancesToMonitors, ··· 187 200 })); 188 201 189 202 await opts.ctx.db.insert(monitorsToPages).values(values).run(); 203 + // Sync to page components 204 + await syncMonitorsToPageInsertMany(opts.ctx.db, values); 190 205 } 191 206 192 207 return selectMonitorSchema.parse(newMonitor); ··· 460 475 })); 461 476 462 477 await opts.ctx.db.insert(monitorsToPages).values(values).run(); 478 + // Sync to page components 479 + await syncMonitorsToPageInsertMany(opts.ctx.db, values); 463 480 } 464 481 465 482 const removedPages = currentMonitorPages ··· 476 493 ), 477 494 ) 478 495 .run(); 496 + // Sync delete to page components 497 + for (const pageId of removedPages) { 498 + await syncMonitorsToPageDelete(opts.ctx.db, { 499 + monitorId: currentMonitor.id, 500 + pageId, 501 + }); 502 + } 479 503 } 480 504 }), 481 505 ··· 593 617 await tx 594 618 .delete(maintenancesToMonitors) 595 619 .where(eq(maintenancesToMonitors.monitorId, monitorToDelete.id)); 620 + // Sync deletes to page components 621 + await syncMonitorsToPageDeleteByMonitors(tx, [monitorToDelete.id]); 622 + await syncStatusReportToMonitorDeleteByMonitors(tx, [ 623 + monitorToDelete.id, 624 + ]); 625 + await syncMaintenanceToMonitorDeleteByMonitors(tx, [ 626 + monitorToDelete.id, 627 + ]); 596 628 }); 597 629 }), 598 630 ··· 639 671 await tx 640 672 .delete(maintenancesToMonitors) 641 673 .where(inArray(maintenancesToMonitors.monitorId, opts.input.ids)); 674 + // Sync deletes to page components 675 + await syncMonitorsToPageDeleteByMonitors(tx, opts.input.ids); 676 + await syncStatusReportToMonitorDeleteByMonitors(tx, opts.input.ids); 677 + await syncMaintenanceToMonitorDeleteByMonitors(tx, opts.input.ids); 642 678 }); 643 679 }), 644 680 ··· 1250 1286 inArray(monitorsToPages.pageId, pageIdsToDelete), 1251 1287 ), 1252 1288 ); 1289 + // Sync delete to page components 1290 + for (const pageId of pageIdsToDelete) { 1291 + await syncMonitorsToPageDelete(tx, { monitorId: input.id, pageId }); 1292 + } 1253 1293 } 1254 1294 1255 1295 if (pageIdsToInsert.length > 0) { 1256 - await tx.insert(monitorsToPages).values( 1257 - pageIdsToInsert.map((pageId) => ({ 1258 - monitorId: input.id, 1259 - pageId, 1260 - })), 1261 - ); 1296 + const values = pageIdsToInsert.map((pageId) => ({ 1297 + monitorId: input.id, 1298 + pageId, 1299 + })); 1300 + await tx.insert(monitorsToPages).values(values); 1301 + // Sync to page components 1302 + await syncMonitorsToPageInsertMany(tx, values); 1262 1303 } 1263 1304 1264 1305 await tx
+58 -16
packages/api/src/router/page.ts
··· 11 11 isNull, 12 12 lte, 13 13 sql, 14 + syncMonitorGroupDeleteMany, 15 + syncMonitorGroupInsert, 16 + syncMonitorsToPageDelete, 17 + syncMonitorsToPageDeleteByPage, 18 + syncMonitorsToPageInsertMany, 14 19 } from "@openstatus/db"; 15 20 import { 16 21 incidentTable, ··· 142 147 })); 143 148 144 149 await opts.ctx.db.insert(monitorsToPages).values(values).run(); 150 + // Sync to page components 151 + await syncMonitorsToPageInsertMany(opts.ctx.db, values); 145 152 } 146 153 147 154 return newPage; ··· 241 248 eq(monitorsToPages.pageId, currentPage.id), 242 249 ), 243 250 ); 251 + // Sync delete to page components 252 + for (const monitorId of removedMonitors) { 253 + await syncMonitorsToPageDelete(opts.ctx.db, { 254 + monitorId, 255 + pageId: currentPage.id, 256 + }); 257 + } 244 258 } 245 259 246 260 const values = monitors.map(({ monitorId }, index) => ({ ··· 257 271 target: [monitorsToPages.monitorId, monitorsToPages.pageId], 258 272 set: { order: sql.raw("excluded.`order`") }, 259 273 }); 274 + // Sync new monitors to page components (existing ones will be ignored due to onConflictDoNothing) 275 + await syncMonitorsToPageInsertMany(opts.ctx.db, values); 260 276 } 261 277 }), 262 278 delete: protectedProcedure ··· 947 963 } 948 964 949 965 await opts.ctx.db.transaction(async (tx) => { 966 + // Get existing monitor groups to delete from page components 967 + const existingGroups = await tx.query.monitorGroup.findMany({ 968 + where: eq(monitorGroup.pageId, opts.input.id), 969 + }); 970 + const existingGroupIds = existingGroups.map((g) => g.id); 971 + 950 972 // Delete child records first to avoid foreign key constraint violation 951 973 await tx 952 974 .delete(monitorsToPages) ··· 955 977 .delete(monitorGroup) 956 978 .where(eq(monitorGroup.pageId, opts.input.id)); 957 979 980 + // Sync deletes to page components 981 + await syncMonitorsToPageDeleteByPage(tx, opts.input.id); 982 + if (existingGroupIds.length > 0) { 983 + await syncMonitorGroupDeleteMany(tx, existingGroupIds); 984 + } 985 + 958 986 if (opts.input.groups.length > 0) { 959 987 const monitorGroups = await tx 960 988 .insert(monitorGroup) ··· 967 995 ) 968 996 .returning(); 969 997 970 - await tx.insert(monitorsToPages).values( 971 - opts.input.groups.flatMap((g, i) => 972 - g.monitors.map((m) => ({ 973 - pageId: opts.input.id, 974 - monitorId: m.id, 975 - order: g.order, 976 - monitorGroupId: monitorGroups[i].id, 977 - groupOrder: m.order, 978 - })), 979 - ), 980 - ); 981 - } 998 + // Sync new monitor groups to page component groups 999 + for (const group of monitorGroups) { 1000 + await syncMonitorGroupInsert(tx, { 1001 + id: group.id, 1002 + workspaceId: opts.ctx.workspace.id, 1003 + pageId: opts.input.id, 1004 + name: group.name, 1005 + }); 1006 + } 982 1007 983 - if (opts.input.monitors.length > 0) { 984 - await tx.insert(monitorsToPages).values( 985 - opts.input.monitors.map((m) => ({ 1008 + const groupMonitorValues = opts.input.groups.flatMap((g, i) => 1009 + g.monitors.map((m) => ({ 986 1010 pageId: opts.input.id, 987 1011 monitorId: m.id, 988 - order: m.order, 1012 + order: g.order, 1013 + monitorGroupId: monitorGroups[i].id, 1014 + groupOrder: m.order, 989 1015 })), 990 1016 ); 1017 + 1018 + await tx.insert(monitorsToPages).values(groupMonitorValues); 1019 + // Sync to page components 1020 + await syncMonitorsToPageInsertMany(tx, groupMonitorValues); 1021 + } 1022 + 1023 + if (opts.input.monitors.length > 0) { 1024 + const monitorValues = opts.input.monitors.map((m) => ({ 1025 + pageId: opts.input.id, 1026 + monitorId: m.id, 1027 + order: m.order, 1028 + })); 1029 + 1030 + await tx.insert(monitorsToPages).values(monitorValues); 1031 + // Sync to page components 1032 + await syncMonitorsToPageInsertMany(tx, monitorValues); 991 1033 } 992 1034 }); 993 1035 }),
+29
packages/api/src/router/statusReport.ts
··· 9 9 gte, 10 10 inArray, 11 11 sql, 12 + syncStatusReportToMonitorDeleteByStatusReport, 13 + syncStatusReportToMonitorInsertMany, 12 14 } from "@openstatus/db"; 13 15 import { 14 16 insertStatusReportSchema, ··· 57 59 ) 58 60 .returning() 59 61 .get(); 62 + // Sync to page components 63 + await syncStatusReportToMonitorInsertMany( 64 + opts.ctx.db, 65 + newStatusReport.id, 66 + monitors, 67 + ); 60 68 } 61 69 62 70 return newStatusReport; ··· 143 151 })); 144 152 145 153 await opts.ctx.db.insert(monitorsToStatusReport).values(values).run(); 154 + // Sync to page components 155 + await syncStatusReportToMonitorInsertMany( 156 + opts.ctx.db, 157 + currentStatusReport.id, 158 + addedMonitors, 159 + ); 146 160 } 147 161 148 162 const removedMonitors = currentMonitorsToStatusReport ··· 159 173 ), 160 174 ) 161 175 .run(); 176 + // Sync delete is handled by cascade on page_component deletion 162 177 } 163 178 164 179 return currentStatusReport; ··· 476 491 ) 477 492 .returning() 478 493 .get(); 494 + // Sync to page components 495 + await syncStatusReportToMonitorInsertMany( 496 + tx, 497 + newStatusReport.id, 498 + opts.input.monitors, 499 + ); 479 500 } 480 501 481 502 return { ··· 516 537 .delete(monitorsToStatusReport) 517 538 .where(eq(monitorsToStatusReport.statusReportId, opts.input.id)) 518 539 .run(); 540 + // Sync delete to page components 541 + await syncStatusReportToMonitorDeleteByStatusReport(tx, opts.input.id); 519 542 520 543 if (opts.input.monitors.length > 0) { 521 544 await tx ··· 527 550 })), 528 551 ) 529 552 .run(); 553 + // Sync to page components 554 + await syncStatusReportToMonitorInsertMany( 555 + tx, 556 + opts.input.id, 557 + opts.input.monitors, 558 + ); 530 559 } 531 560 }); 532 561 }),
+608
packages/api/src/router/sync.test.ts
··· 1 + import { afterAll, beforeAll, describe, expect, test } from "bun:test"; 2 + import { and, db, eq, inArray } from "@openstatus/db"; 3 + import { 4 + maintenance, 5 + maintenancesToMonitors, 6 + maintenancesToPageComponents, 7 + monitor, 8 + monitorGroup, 9 + monitorsToPages, 10 + monitorsToStatusReport, 11 + page, 12 + pageComponent, 13 + pageComponentGroup, 14 + statusReport, 15 + statusReportUpdate, 16 + statusReportsToPageComponents, 17 + } from "@openstatus/db/src/schema"; 18 + import { flyRegions } from "@openstatus/db/src/schema/constants"; 19 + 20 + import { appRouter } from "../root"; 21 + import { createInnerTRPCContext } from "../trpc"; 22 + 23 + /** 24 + * Sync Tests: Verify that mutations to legacy tables also sync to new page_component tables 25 + * 26 + * Table mappings: 27 + * - monitor_group -> page_component_groups 28 + * - monitors_to_pages -> page_component 29 + * - status_report_to_monitors -> status_report_to_page_component 30 + * - maintenance_to_monitor -> maintenance_to_page_component 31 + */ 32 + 33 + function getTestContext(limits?: unknown) { 34 + return createInnerTRPCContext({ 35 + req: undefined, 36 + session: { 37 + user: { 38 + id: "1", 39 + }, 40 + }, 41 + workspace: { 42 + id: 1, 43 + // @ts-expect-error - test context with partial limits 44 + limits: limits || { 45 + monitors: 100, 46 + periodicity: ["30s", "1m", "5m", "10m", "30m", "1h"], 47 + regions: flyRegions, 48 + "status-pages": 10, 49 + maintenance: true, 50 + notifications: 10, 51 + "status-subscribers": true, 52 + sms: false, 53 + pagerduty: true, 54 + "password-protection": true, 55 + "email-domain-protection": true, 56 + "custom-domain": true, 57 + }, 58 + }, 59 + }); 60 + } 61 + 62 + // Test data identifiers 63 + const TEST_PREFIX = "sync-test"; 64 + let testPageId: number; 65 + let testMonitorId: number; 66 + 67 + const monitorData = { 68 + name: `${TEST_PREFIX}-monitor`, 69 + url: "https://sync-test.example.com", 70 + jobType: "http" as const, 71 + method: "GET" as const, 72 + periodicity: "1m" as const, 73 + regions: [flyRegions[0]], 74 + statusAssertions: [], 75 + headerAssertions: [], 76 + textBodyAssertions: [], 77 + notifications: [], 78 + pages: [] as number[], 79 + tags: [], 80 + }; 81 + 82 + beforeAll(async () => { 83 + // Clean up any existing test data 84 + await db 85 + .delete(pageComponent) 86 + .where(eq(pageComponent.name, `${TEST_PREFIX}-monitor`)); 87 + await db.delete(page).where(eq(page.slug, `${TEST_PREFIX}-page`)); 88 + await db.delete(monitor).where(eq(monitor.name, `${TEST_PREFIX}-monitor`)); 89 + await db 90 + .delete(monitor) 91 + .where(eq(monitor.name, `${TEST_PREFIX}-deletable-monitor`)); 92 + 93 + // Create test page first 94 + const testPage = await db 95 + .insert(page) 96 + .values({ 97 + workspaceId: 1, 98 + title: "Sync Test Page", 99 + description: "A test page for sync tests", 100 + slug: `${TEST_PREFIX}-page`, 101 + customDomain: "", 102 + }) 103 + .returning() 104 + .get(); 105 + testPageId = testPage.id; 106 + 107 + // Create test monitor using tRPC 108 + const ctx = getTestContext(); 109 + const caller = appRouter.createCaller(ctx); 110 + const createdMonitor = await caller.monitor.create(monitorData); 111 + testMonitorId = createdMonitor.id; 112 + }); 113 + 114 + afterAll(async () => { 115 + // Clean up test data in correct order (dependencies first) 116 + await db 117 + .delete(maintenancesToPageComponents) 118 + .where( 119 + inArray( 120 + maintenancesToPageComponents.pageComponentId, 121 + db 122 + .select({ id: pageComponent.id }) 123 + .from(pageComponent) 124 + .where(eq(pageComponent.pageId, testPageId)), 125 + ), 126 + ); 127 + await db 128 + .delete(statusReportsToPageComponents) 129 + .where( 130 + inArray( 131 + statusReportsToPageComponents.pageComponentId, 132 + db 133 + .select({ id: pageComponent.id }) 134 + .from(pageComponent) 135 + .where(eq(pageComponent.pageId, testPageId)), 136 + ), 137 + ); 138 + await db.delete(pageComponent).where(eq(pageComponent.pageId, testPageId)); 139 + await db 140 + .delete(pageComponentGroup) 141 + .where(eq(pageComponentGroup.pageId, testPageId)); 142 + await db.delete(monitorGroup).where(eq(monitorGroup.pageId, testPageId)); 143 + await db 144 + .delete(monitorsToPages) 145 + .where(eq(monitorsToPages.pageId, testPageId)); 146 + await db 147 + .delete(maintenancesToMonitors) 148 + .where(eq(maintenancesToMonitors.monitorId, testMonitorId)); 149 + await db 150 + .delete(monitorsToStatusReport) 151 + .where(eq(monitorsToStatusReport.monitorId, testMonitorId)); 152 + await db 153 + .delete(statusReportUpdate) 154 + .where( 155 + inArray( 156 + statusReportUpdate.statusReportId, 157 + db 158 + .select({ id: statusReport.id }) 159 + .from(statusReport) 160 + .where(eq(statusReport.pageId, testPageId)), 161 + ), 162 + ); 163 + await db.delete(statusReport).where(eq(statusReport.pageId, testPageId)); 164 + await db.delete(maintenance).where(eq(maintenance.pageId, testPageId)); 165 + await db.delete(page).where(eq(page.slug, `${TEST_PREFIX}-page`)); 166 + await db.delete(monitor).where(eq(monitor.name, `${TEST_PREFIX}-monitor`)); 167 + await db 168 + .delete(monitor) 169 + .where(eq(monitor.name, `${TEST_PREFIX}-deletable-monitor`)); 170 + }); 171 + 172 + describe("Sync: monitors_to_pages -> page_component", () => { 173 + test("Creating monitor-to-page relation syncs to page_component", async () => { 174 + const ctx = getTestContext(); 175 + const caller = appRouter.createCaller(ctx); 176 + 177 + // Update monitor to add it to the test page 178 + await caller.monitor.update({ 179 + ...monitorData, 180 + id: testMonitorId, 181 + pages: [testPageId], 182 + }); 183 + 184 + // Verify monitors_to_pages was created 185 + const monitorToPage = await db.query.monitorsToPages.findFirst({ 186 + where: and( 187 + eq(monitorsToPages.monitorId, testMonitorId), 188 + eq(monitorsToPages.pageId, testPageId), 189 + ), 190 + }); 191 + expect(monitorToPage).toBeDefined(); 192 + 193 + // Verify page_component was synced 194 + const component = await db.query.pageComponent.findFirst({ 195 + where: and( 196 + eq(pageComponent.monitorId, testMonitorId), 197 + eq(pageComponent.pageId, testPageId), 198 + ), 199 + }); 200 + expect(component).toBeDefined(); 201 + expect(component?.type).toBe("monitor"); 202 + expect(component?.workspaceId).toBe(1); 203 + }); 204 + 205 + test("Removing monitor-to-page relation syncs delete to page_component", async () => { 206 + const ctx = getTestContext(); 207 + const caller = appRouter.createCaller(ctx); 208 + 209 + // First add the monitor to page if not already 210 + await caller.monitor.update({ 211 + ...monitorData, 212 + id: testMonitorId, 213 + pages: [testPageId], 214 + }); 215 + 216 + // Verify page_component exists 217 + let component = await db.query.pageComponent.findFirst({ 218 + where: and( 219 + eq(pageComponent.monitorId, testMonitorId), 220 + eq(pageComponent.pageId, testPageId), 221 + ), 222 + }); 223 + expect(component).toBeDefined(); 224 + 225 + // Remove monitor from page 226 + await caller.monitor.update({ 227 + ...monitorData, 228 + id: testMonitorId, 229 + pages: [], 230 + }); 231 + 232 + // Verify page_component was deleted 233 + component = await db.query.pageComponent.findFirst({ 234 + where: and( 235 + eq(pageComponent.monitorId, testMonitorId), 236 + eq(pageComponent.pageId, testPageId), 237 + ), 238 + }); 239 + expect(component).toBeUndefined(); 240 + }); 241 + }); 242 + 243 + describe("Sync: page.updateMonitors -> page_component and page_component_groups", () => { 244 + test("Adding monitors with groups syncs to page_component and page_component_groups", async () => { 245 + const ctx = getTestContext(); 246 + const caller = appRouter.createCaller(ctx); 247 + 248 + // Update page with monitor in a group 249 + await caller.page.updateMonitors({ 250 + id: testPageId, 251 + monitors: [], 252 + groups: [ 253 + { 254 + name: `${TEST_PREFIX}-group`, 255 + order: 0, 256 + monitors: [{ id: testMonitorId, order: 0 }], 257 + }, 258 + ], 259 + }); 260 + 261 + // Verify monitor_group was created 262 + const group = await db.query.monitorGroup.findFirst({ 263 + where: and( 264 + eq(monitorGroup.pageId, testPageId), 265 + eq(monitorGroup.name, `${TEST_PREFIX}-group`), 266 + ), 267 + }); 268 + expect(group).toBeDefined(); 269 + 270 + if (!group) { 271 + throw new Error("Group not found"); 272 + } 273 + 274 + // Verify page_component_groups was synced 275 + const componentGroup = await db.query.pageComponentGroup.findFirst({ 276 + where: eq(pageComponentGroup.id, group.id), 277 + }); 278 + expect(componentGroup).toBeDefined(); 279 + expect(componentGroup?.name).toBe(`${TEST_PREFIX}-group`); 280 + expect(componentGroup?.pageId).toBe(testPageId); 281 + 282 + // Verify page_component was synced with group reference 283 + const component = await db.query.pageComponent.findFirst({ 284 + where: and( 285 + eq(pageComponent.monitorId, testMonitorId), 286 + eq(pageComponent.pageId, testPageId), 287 + ), 288 + }); 289 + expect(component).toBeDefined(); 290 + expect(component?.groupId).toBe(group.id); 291 + }); 292 + 293 + test("Updating page monitors syncs changes to page_component", async () => { 294 + const ctx = getTestContext(); 295 + const caller = appRouter.createCaller(ctx); 296 + 297 + // First, set up with a group 298 + await caller.page.updateMonitors({ 299 + id: testPageId, 300 + monitors: [], 301 + groups: [ 302 + { 303 + name: `${TEST_PREFIX}-group`, 304 + order: 0, 305 + monitors: [{ id: testMonitorId, order: 0 }], 306 + }, 307 + ], 308 + }); 309 + 310 + // Now update to remove the group and add monitor directly 311 + await caller.page.updateMonitors({ 312 + id: testPageId, 313 + monitors: [{ id: testMonitorId, order: 0 }], 314 + groups: [], 315 + }); 316 + 317 + // Verify monitor_group was deleted 318 + const group = await db.query.monitorGroup.findFirst({ 319 + where: and( 320 + eq(monitorGroup.pageId, testPageId), 321 + eq(monitorGroup.name, `${TEST_PREFIX}-group`), 322 + ), 323 + }); 324 + expect(group).toBeUndefined(); 325 + 326 + // Verify page_component still exists but without group 327 + const component = await db.query.pageComponent.findFirst({ 328 + where: and( 329 + eq(pageComponent.monitorId, testMonitorId), 330 + eq(pageComponent.pageId, testPageId), 331 + ), 332 + }); 333 + expect(component).toBeDefined(); 334 + expect(component?.groupId).toBeNull(); 335 + }); 336 + }); 337 + 338 + describe("Sync: maintenance_to_monitor -> maintenance_to_page_component", () => { 339 + let testMaintenanceId: number; 340 + 341 + beforeAll(async () => { 342 + const ctx = getTestContext(); 343 + const caller = appRouter.createCaller(ctx); 344 + 345 + // Ensure monitor is on the page first 346 + await caller.monitor.update({ 347 + ...monitorData, 348 + id: testMonitorId, 349 + pages: [testPageId], 350 + }); 351 + }); 352 + 353 + afterAll(async () => { 354 + if (testMaintenanceId) { 355 + await db 356 + .delete(maintenancesToPageComponents) 357 + .where( 358 + eq(maintenancesToPageComponents.maintenanceId, testMaintenanceId), 359 + ); 360 + await db 361 + .delete(maintenancesToMonitors) 362 + .where(eq(maintenancesToMonitors.maintenanceId, testMaintenanceId)); 363 + await db.delete(maintenance).where(eq(maintenance.id, testMaintenanceId)); 364 + } 365 + }); 366 + 367 + test("Creating maintenance with monitors syncs to maintenance_to_page_component", async () => { 368 + const ctx = getTestContext(); 369 + const caller = appRouter.createCaller(ctx); 370 + 371 + const from = new Date(); 372 + const to = new Date(from.getTime() + 1000 * 60 * 60); 373 + 374 + const createdMaintenance = await caller.maintenance.new({ 375 + title: `${TEST_PREFIX} Maintenance`, 376 + message: "Test maintenance for sync", 377 + startDate: from, 378 + endDate: to, 379 + pageId: testPageId, 380 + monitors: [testMonitorId], 381 + }); 382 + testMaintenanceId = createdMaintenance.id; 383 + 384 + // Verify maintenance_to_monitor was created 385 + const maintenanceToMonitor = 386 + await db.query.maintenancesToMonitors.findFirst({ 387 + where: and( 388 + eq(maintenancesToMonitors.maintenanceId, testMaintenanceId), 389 + eq(maintenancesToMonitors.monitorId, testMonitorId), 390 + ), 391 + }); 392 + expect(maintenanceToMonitor).toBeDefined(); 393 + 394 + // Verify maintenance_to_page_component was synced 395 + const component = await db.query.pageComponent.findFirst({ 396 + where: and( 397 + eq(pageComponent.monitorId, testMonitorId), 398 + eq(pageComponent.pageId, testPageId), 399 + ), 400 + }); 401 + 402 + if (component) { 403 + const maintenanceToComponent = 404 + await db.query.maintenancesToPageComponents.findFirst({ 405 + where: and( 406 + eq(maintenancesToPageComponents.maintenanceId, testMaintenanceId), 407 + eq(maintenancesToPageComponents.pageComponentId, component.id), 408 + ), 409 + }); 410 + expect(maintenanceToComponent).toBeDefined(); 411 + } 412 + }); 413 + 414 + test("Updating maintenance monitors syncs to maintenance_to_page_component", async () => { 415 + const ctx = getTestContext(); 416 + const caller = appRouter.createCaller(ctx); 417 + 418 + // Skip if no maintenance was created 419 + if (!testMaintenanceId) return; 420 + 421 + const from = new Date(); 422 + const to = new Date(from.getTime() + 1000 * 60 * 60); 423 + 424 + // Update maintenance to remove monitors 425 + await caller.maintenance.update({ 426 + id: testMaintenanceId, 427 + title: `${TEST_PREFIX} Maintenance`, 428 + message: "Updated maintenance", 429 + startDate: from, 430 + endDate: to, 431 + monitors: [], 432 + }); 433 + 434 + // Verify maintenance_to_monitor was deleted 435 + const maintenanceToMonitor = 436 + await db.query.maintenancesToMonitors.findFirst({ 437 + where: and( 438 + eq(maintenancesToMonitors.maintenanceId, testMaintenanceId), 439 + eq(maintenancesToMonitors.monitorId, testMonitorId), 440 + ), 441 + }); 442 + expect(maintenanceToMonitor).toBeUndefined(); 443 + 444 + // Verify maintenance_to_page_component was also deleted 445 + const maintenanceToComponent = 446 + await db.query.maintenancesToPageComponents.findFirst({ 447 + where: eq( 448 + maintenancesToPageComponents.maintenanceId, 449 + testMaintenanceId, 450 + ), 451 + }); 452 + expect(maintenanceToComponent).toBeUndefined(); 453 + }); 454 + }); 455 + 456 + describe("Sync: status_report_to_monitors -> status_report_to_page_component", () => { 457 + let testStatusReportId: number; 458 + 459 + beforeAll(async () => { 460 + const ctx = getTestContext(); 461 + const caller = appRouter.createCaller(ctx); 462 + 463 + // Ensure monitor is on the page first 464 + await caller.monitor.update({ 465 + ...monitorData, 466 + id: testMonitorId, 467 + pages: [testPageId], 468 + }); 469 + }); 470 + 471 + afterAll(async () => { 472 + if (testStatusReportId) { 473 + await db 474 + .delete(statusReportsToPageComponents) 475 + .where( 476 + eq(statusReportsToPageComponents.statusReportId, testStatusReportId), 477 + ); 478 + await db 479 + .delete(monitorsToStatusReport) 480 + .where(eq(monitorsToStatusReport.statusReportId, testStatusReportId)); 481 + await db 482 + .delete(statusReportUpdate) 483 + .where(eq(statusReportUpdate.statusReportId, testStatusReportId)); 484 + await db 485 + .delete(statusReport) 486 + .where(eq(statusReport.id, testStatusReportId)); 487 + } 488 + }); 489 + 490 + test("Creating status report with monitors syncs to status_report_to_page_component", async () => { 491 + const ctx = getTestContext(); 492 + const caller = appRouter.createCaller(ctx); 493 + 494 + const createdReport = await caller.statusReport.create({ 495 + title: `${TEST_PREFIX} Status Report`, 496 + status: "investigating", 497 + message: "Test status report for sync", 498 + pageId: testPageId, 499 + monitors: [testMonitorId], 500 + date: new Date(), 501 + }); 502 + testStatusReportId = createdReport.statusReportId; 503 + 504 + // Verify status_report_to_monitors was created 505 + const reportToMonitor = await db.query.monitorsToStatusReport.findFirst({ 506 + where: and( 507 + eq(monitorsToStatusReport.statusReportId, testStatusReportId), 508 + eq(monitorsToStatusReport.monitorId, testMonitorId), 509 + ), 510 + }); 511 + expect(reportToMonitor).toBeDefined(); 512 + 513 + // Verify status_report_to_page_component was synced 514 + const component = await db.query.pageComponent.findFirst({ 515 + where: and( 516 + eq(pageComponent.monitorId, testMonitorId), 517 + eq(pageComponent.pageId, testPageId), 518 + ), 519 + }); 520 + 521 + if (component) { 522 + const reportToComponent = 523 + await db.query.statusReportsToPageComponents.findFirst({ 524 + where: and( 525 + eq( 526 + statusReportsToPageComponents.statusReportId, 527 + testStatusReportId, 528 + ), 529 + eq(statusReportsToPageComponents.pageComponentId, component.id), 530 + ), 531 + }); 532 + expect(reportToComponent).toBeDefined(); 533 + } 534 + }); 535 + 536 + test("Updating status report monitors syncs to status_report_to_page_component", async () => { 537 + const ctx = getTestContext(); 538 + const caller = appRouter.createCaller(ctx); 539 + 540 + // Skip if no status report was created 541 + if (!testStatusReportId) return; 542 + 543 + // Update status to remove monitors (using updateStatus procedure) 544 + await caller.statusReport.updateStatus({ 545 + id: testStatusReportId, 546 + status: "resolved", 547 + monitors: [], 548 + title: `${TEST_PREFIX} Status Report`, 549 + }); 550 + 551 + // Verify status_report_to_monitors was deleted 552 + const reportToMonitor = await db.query.monitorsToStatusReport.findFirst({ 553 + where: and( 554 + eq(monitorsToStatusReport.statusReportId, testStatusReportId), 555 + eq(monitorsToStatusReport.monitorId, testMonitorId), 556 + ), 557 + }); 558 + expect(reportToMonitor).toBeUndefined(); 559 + 560 + // Verify status_report_to_page_component was also deleted 561 + const reportToComponent = 562 + await db.query.statusReportsToPageComponents.findFirst({ 563 + where: eq( 564 + statusReportsToPageComponents.statusReportId, 565 + testStatusReportId, 566 + ), 567 + }); 568 + expect(reportToComponent).toBeUndefined(); 569 + }); 570 + }); 571 + 572 + describe("Sync: monitor deletion cascades to page_component tables", () => { 573 + let deletableMonitorId: number; 574 + 575 + beforeAll(async () => { 576 + const ctx = getTestContext(); 577 + const caller = appRouter.createCaller(ctx); 578 + 579 + // Create a monitor specifically for deletion tests 580 + const deletableMonitor = await caller.monitor.create({ 581 + ...monitorData, 582 + name: `${TEST_PREFIX}-deletable-monitor`, 583 + url: "https://delete-test.example.com", 584 + pages: [testPageId], 585 + }); 586 + deletableMonitorId = deletableMonitor.id; 587 + }); 588 + 589 + test("Deleting monitor removes related page_component entries", async () => { 590 + const ctx = getTestContext(); 591 + const caller = appRouter.createCaller(ctx); 592 + 593 + // Verify page_component exists before deletion 594 + let component = await db.query.pageComponent.findFirst({ 595 + where: eq(pageComponent.monitorId, deletableMonitorId), 596 + }); 597 + expect(component).toBeDefined(); 598 + 599 + // Delete the monitor 600 + await caller.monitor.delete({ id: deletableMonitorId }); 601 + 602 + // Verify page_component was removed 603 + component = await db.query.pageComponent.findFirst({ 604 + where: eq(pageComponent.monitorId, deletableMonitorId), 605 + }); 606 + expect(component).toBeUndefined(); 607 + }); 608 + });
+127
packages/db/drizzle/0054_bitter_lilandra.sql
··· 1 + CREATE TABLE `maintenance_to_page_component` ( 2 + `maintenance_id` integer NOT NULL, 3 + `page_component_id` integer NOT NULL, 4 + `created_at` integer DEFAULT (strftime('%s', 'now')), 5 + PRIMARY KEY(`maintenance_id`, `page_component_id`), 6 + FOREIGN KEY (`maintenance_id`) REFERENCES `maintenance`(`id`) ON UPDATE no action ON DELETE cascade, 7 + FOREIGN KEY (`page_component_id`) REFERENCES `page_component`(`id`) ON UPDATE no action ON DELETE cascade 8 + ); 9 + --> statement-breakpoint 10 + CREATE TABLE `page_component` ( 11 + `id` integer PRIMARY KEY NOT NULL, 12 + `workspace_id` integer NOT NULL, 13 + `page_id` integer NOT NULL, 14 + `type` text DEFAULT 'monitor' NOT NULL, 15 + `monitor_id` integer, 16 + `name` text NOT NULL, 17 + `description` text, 18 + `order` integer DEFAULT 0, 19 + `group_id` integer, 20 + `group_order` integer DEFAULT 0, 21 + `created_at` integer DEFAULT (strftime('%s', 'now')), 22 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 23 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE cascade, 24 + FOREIGN KEY (`page_id`) REFERENCES `page`(`id`) ON UPDATE no action ON DELETE cascade, 25 + FOREIGN KEY (`monitor_id`) REFERENCES `monitor`(`id`) ON UPDATE no action ON DELETE cascade, 26 + FOREIGN KEY (`group_id`) REFERENCES `page_component_groups`(`id`) ON UPDATE no action ON DELETE set null, 27 + CONSTRAINT "page_component_type_check" CHECK("page_component"."type" = 'monitor' AND "page_component"."monitor_id" IS NOT NULL OR "page_component"."type" = 'external' AND "page_component"."monitor_id" IS NULL) 28 + ); 29 + --> statement-breakpoint 30 + CREATE UNIQUE INDEX `page_component_page_id_monitor_id_unique` ON `page_component` (`page_id`,`monitor_id`);--> statement-breakpoint 31 + CREATE TABLE `status_report_to_page_component` ( 32 + `status_report_id` integer NOT NULL, 33 + `page_component_id` integer NOT NULL, 34 + `created_at` integer DEFAULT (strftime('%s', 'now')), 35 + PRIMARY KEY(`status_report_id`, `page_component_id`), 36 + FOREIGN KEY (`status_report_id`) REFERENCES `status_report`(`id`) ON UPDATE no action ON DELETE cascade, 37 + FOREIGN KEY (`page_component_id`) REFERENCES `page_component`(`id`) ON UPDATE no action ON DELETE cascade 38 + ); 39 + --> statement-breakpoint 40 + CREATE TABLE `page_component_groups` ( 41 + `id` integer PRIMARY KEY NOT NULL, 42 + `workspace_id` integer NOT NULL, 43 + `page_id` integer NOT NULL, 44 + `name` text NOT NULL, 45 + `created_at` integer DEFAULT (strftime('%s', 'now')), 46 + `updated_at` integer DEFAULT (strftime('%s', 'now')), 47 + FOREIGN KEY (`workspace_id`) REFERENCES `workspace`(`id`) ON UPDATE no action ON DELETE cascade, 48 + FOREIGN KEY (`page_id`) REFERENCES `page`(`id`) ON UPDATE no action ON DELETE cascade 49 + ); 50 + --> statement-breakpoint 51 + -- Data Migration: monitors_to_pages → page_component 52 + -- This section migrates data from the old structure to the new page_component structure 53 + -- Step 0: Migrate monitor_group to page_component_groups 54 + INSERT OR IGNORE INTO `page_component_groups` ( 55 + `id`, 56 + `workspace_id`, 57 + `page_id`, 58 + `name`, 59 + `created_at`, 60 + `updated_at` 61 + ) 62 + SELECT 63 + mg.id, 64 + mg.workspace_id, 65 + mg.page_id, 66 + mg.name, 67 + mg.created_at, 68 + mg.updated_at 69 + FROM `monitor_group` mg; 70 + --> statement-breakpoint 71 + -- Step 1: Migrate monitors_to_pages to page_component 72 + INSERT OR IGNORE INTO `page_component` ( 73 + `workspace_id`, 74 + `page_id`, 75 + `type`, 76 + `monitor_id`, 77 + `name`, 78 + `order`, 79 + `group_id`, 80 + `group_order`, 81 + `created_at`, 82 + `updated_at` 83 + ) 84 + SELECT 85 + m.workspace_id, 86 + mtp.page_id, 87 + 'monitor' as type, 88 + mtp.monitor_id, 89 + COALESCE(m.external_name, m.name, 'Unnamed Monitor') as name, 90 + COALESCE(mtp.`order`, 0), 91 + mtp.monitor_group_id as group_id, 92 + COALESCE(mtp.group_order, 0) as group_order, 93 + mtp.created_at, 94 + strftime('%s', 'now') as updated_at 95 + FROM `monitors_to_pages` mtp 96 + INNER JOIN `monitor` m ON mtp.monitor_id = m.id 97 + WHERE m.workspace_id IS NOT NULL; 98 + --> statement-breakpoint 99 + -- Step 2: Migrate status_report_to_monitors to status_report_to_page_component 100 + INSERT OR IGNORE INTO `status_report_to_page_component` ( 101 + `status_report_id`, 102 + `page_component_id`, 103 + `created_at` 104 + ) 105 + SELECT DISTINCT 106 + srtm.status_report_id, 107 + pc.id as page_component_id, 108 + srtm.created_at 109 + FROM `status_report_to_monitors` srtm 110 + INNER JOIN `status_report` sr ON srtm.status_report_id = sr.id 111 + INNER JOIN `page_component` pc ON srtm.monitor_id = pc.monitor_id 112 + WHERE (sr.page_id = pc.page_id OR sr.page_id IS NULL); 113 + --> statement-breakpoint 114 + -- Step 3: Migrate maintenance_to_monitor to maintenance_to_page_component 115 + INSERT OR IGNORE INTO `maintenance_to_page_component` ( 116 + `maintenance_id`, 117 + `page_component_id`, 118 + `created_at` 119 + ) 120 + SELECT DISTINCT 121 + mtm.maintenance_id, 122 + pc.id as page_component_id, 123 + mtm.created_at 124 + FROM `maintenance_to_monitor` mtm 125 + INNER JOIN `maintenance` m ON mtm.maintenance_id = m.id 126 + INNER JOIN `page_component` pc ON mtm.monitor_id = pc.monitor_id 127 + WHERE (m.page_id = pc.page_id OR m.page_id IS NULL);
+3459
packages/db/drizzle/meta/0054_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "fa90adb7-69d9-419c-baf9-3a73922e9e43", 5 + "prevId": "cd3deb34-e4c8-4ae2-aa4e-bc0f7d9517f5", 6 + "tables": { 7 + "workspace": { 8 + "name": "workspace", 9 + "columns": { 10 + "id": { 11 + "name": "id", 12 + "type": "integer", 13 + "primaryKey": true, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "slug": { 18 + "name": "slug", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + }, 24 + "name": { 25 + "name": "name", 26 + "type": "text", 27 + "primaryKey": false, 28 + "notNull": false, 29 + "autoincrement": false 30 + }, 31 + "stripe_id": { 32 + "name": "stripe_id", 33 + "type": "text(256)", 34 + "primaryKey": false, 35 + "notNull": false, 36 + "autoincrement": false 37 + }, 38 + "subscription_id": { 39 + "name": "subscription_id", 40 + "type": "text", 41 + "primaryKey": false, 42 + "notNull": false, 43 + "autoincrement": false 44 + }, 45 + "plan": { 46 + "name": "plan", 47 + "type": "text", 48 + "primaryKey": false, 49 + "notNull": false, 50 + "autoincrement": false 51 + }, 52 + "ends_at": { 53 + "name": "ends_at", 54 + "type": "integer", 55 + "primaryKey": false, 56 + "notNull": false, 57 + "autoincrement": false 58 + }, 59 + "paid_until": { 60 + "name": "paid_until", 61 + "type": "integer", 62 + "primaryKey": false, 63 + "notNull": false, 64 + "autoincrement": false 65 + }, 66 + "limits": { 67 + "name": "limits", 68 + "type": "text", 69 + "primaryKey": false, 70 + "notNull": true, 71 + "autoincrement": false, 72 + "default": "'{}'" 73 + }, 74 + "created_at": { 75 + "name": "created_at", 76 + "type": "integer", 77 + "primaryKey": false, 78 + "notNull": false, 79 + "autoincrement": false, 80 + "default": "(strftime('%s', 'now'))" 81 + }, 82 + "updated_at": { 83 + "name": "updated_at", 84 + "type": "integer", 85 + "primaryKey": false, 86 + "notNull": false, 87 + "autoincrement": false, 88 + "default": "(strftime('%s', 'now'))" 89 + }, 90 + "dsn": { 91 + "name": "dsn", 92 + "type": "text", 93 + "primaryKey": false, 94 + "notNull": false, 95 + "autoincrement": false 96 + } 97 + }, 98 + "indexes": { 99 + "workspace_slug_unique": { 100 + "name": "workspace_slug_unique", 101 + "columns": [ 102 + "slug" 103 + ], 104 + "isUnique": true 105 + }, 106 + "workspace_stripe_id_unique": { 107 + "name": "workspace_stripe_id_unique", 108 + "columns": [ 109 + "stripe_id" 110 + ], 111 + "isUnique": true 112 + }, 113 + "workspace_id_dsn_unique": { 114 + "name": "workspace_id_dsn_unique", 115 + "columns": [ 116 + "id", 117 + "dsn" 118 + ], 119 + "isUnique": true 120 + } 121 + }, 122 + "foreignKeys": {}, 123 + "compositePrimaryKeys": {}, 124 + "uniqueConstraints": {}, 125 + "checkConstraints": {} 126 + }, 127 + "account": { 128 + "name": "account", 129 + "columns": { 130 + "user_id": { 131 + "name": "user_id", 132 + "type": "integer", 133 + "primaryKey": false, 134 + "notNull": true, 135 + "autoincrement": false 136 + }, 137 + "type": { 138 + "name": "type", 139 + "type": "text", 140 + "primaryKey": false, 141 + "notNull": true, 142 + "autoincrement": false 143 + }, 144 + "provider": { 145 + "name": "provider", 146 + "type": "text", 147 + "primaryKey": false, 148 + "notNull": true, 149 + "autoincrement": false 150 + }, 151 + "provider_account_id": { 152 + "name": "provider_account_id", 153 + "type": "text", 154 + "primaryKey": false, 155 + "notNull": true, 156 + "autoincrement": false 157 + }, 158 + "refresh_token": { 159 + "name": "refresh_token", 160 + "type": "text", 161 + "primaryKey": false, 162 + "notNull": false, 163 + "autoincrement": false 164 + }, 165 + "access_token": { 166 + "name": "access_token", 167 + "type": "text", 168 + "primaryKey": false, 169 + "notNull": false, 170 + "autoincrement": false 171 + }, 172 + "expires_at": { 173 + "name": "expires_at", 174 + "type": "integer", 175 + "primaryKey": false, 176 + "notNull": false, 177 + "autoincrement": false 178 + }, 179 + "token_type": { 180 + "name": "token_type", 181 + "type": "text", 182 + "primaryKey": false, 183 + "notNull": false, 184 + "autoincrement": false 185 + }, 186 + "scope": { 187 + "name": "scope", 188 + "type": "text", 189 + "primaryKey": false, 190 + "notNull": false, 191 + "autoincrement": false 192 + }, 193 + "id_token": { 194 + "name": "id_token", 195 + "type": "text", 196 + "primaryKey": false, 197 + "notNull": false, 198 + "autoincrement": false 199 + }, 200 + "session_state": { 201 + "name": "session_state", 202 + "type": "text", 203 + "primaryKey": false, 204 + "notNull": false, 205 + "autoincrement": false 206 + } 207 + }, 208 + "indexes": {}, 209 + "foreignKeys": { 210 + "account_user_id_user_id_fk": { 211 + "name": "account_user_id_user_id_fk", 212 + "tableFrom": "account", 213 + "tableTo": "user", 214 + "columnsFrom": [ 215 + "user_id" 216 + ], 217 + "columnsTo": [ 218 + "id" 219 + ], 220 + "onDelete": "cascade", 221 + "onUpdate": "no action" 222 + } 223 + }, 224 + "compositePrimaryKeys": { 225 + "account_provider_provider_account_id_pk": { 226 + "columns": [ 227 + "provider", 228 + "provider_account_id" 229 + ], 230 + "name": "account_provider_provider_account_id_pk" 231 + } 232 + }, 233 + "uniqueConstraints": {}, 234 + "checkConstraints": {} 235 + }, 236 + "session": { 237 + "name": "session", 238 + "columns": { 239 + "session_token": { 240 + "name": "session_token", 241 + "type": "text", 242 + "primaryKey": true, 243 + "notNull": true, 244 + "autoincrement": false 245 + }, 246 + "user_id": { 247 + "name": "user_id", 248 + "type": "integer", 249 + "primaryKey": false, 250 + "notNull": true, 251 + "autoincrement": false 252 + }, 253 + "expires": { 254 + "name": "expires", 255 + "type": "integer", 256 + "primaryKey": false, 257 + "notNull": true, 258 + "autoincrement": false 259 + } 260 + }, 261 + "indexes": {}, 262 + "foreignKeys": { 263 + "session_user_id_user_id_fk": { 264 + "name": "session_user_id_user_id_fk", 265 + "tableFrom": "session", 266 + "tableTo": "user", 267 + "columnsFrom": [ 268 + "user_id" 269 + ], 270 + "columnsTo": [ 271 + "id" 272 + ], 273 + "onDelete": "cascade", 274 + "onUpdate": "no action" 275 + } 276 + }, 277 + "compositePrimaryKeys": {}, 278 + "uniqueConstraints": {}, 279 + "checkConstraints": {} 280 + }, 281 + "user": { 282 + "name": "user", 283 + "columns": { 284 + "id": { 285 + "name": "id", 286 + "type": "integer", 287 + "primaryKey": true, 288 + "notNull": true, 289 + "autoincrement": false 290 + }, 291 + "tenant_id": { 292 + "name": "tenant_id", 293 + "type": "text(256)", 294 + "primaryKey": false, 295 + "notNull": false, 296 + "autoincrement": false 297 + }, 298 + "first_name": { 299 + "name": "first_name", 300 + "type": "text", 301 + "primaryKey": false, 302 + "notNull": false, 303 + "autoincrement": false, 304 + "default": "''" 305 + }, 306 + "last_name": { 307 + "name": "last_name", 308 + "type": "text", 309 + "primaryKey": false, 310 + "notNull": false, 311 + "autoincrement": false, 312 + "default": "''" 313 + }, 314 + "photo_url": { 315 + "name": "photo_url", 316 + "type": "text", 317 + "primaryKey": false, 318 + "notNull": false, 319 + "autoincrement": false, 320 + "default": "''" 321 + }, 322 + "name": { 323 + "name": "name", 324 + "type": "text", 325 + "primaryKey": false, 326 + "notNull": false, 327 + "autoincrement": false 328 + }, 329 + "email": { 330 + "name": "email", 331 + "type": "text", 332 + "primaryKey": false, 333 + "notNull": false, 334 + "autoincrement": false, 335 + "default": "''" 336 + }, 337 + "emailVerified": { 338 + "name": "emailVerified", 339 + "type": "integer", 340 + "primaryKey": false, 341 + "notNull": false, 342 + "autoincrement": false 343 + }, 344 + "created_at": { 345 + "name": "created_at", 346 + "type": "integer", 347 + "primaryKey": false, 348 + "notNull": false, 349 + "autoincrement": false, 350 + "default": "(strftime('%s', 'now'))" 351 + }, 352 + "updated_at": { 353 + "name": "updated_at", 354 + "type": "integer", 355 + "primaryKey": false, 356 + "notNull": false, 357 + "autoincrement": false, 358 + "default": "(strftime('%s', 'now'))" 359 + } 360 + }, 361 + "indexes": { 362 + "user_tenant_id_unique": { 363 + "name": "user_tenant_id_unique", 364 + "columns": [ 365 + "tenant_id" 366 + ], 367 + "isUnique": true 368 + } 369 + }, 370 + "foreignKeys": {}, 371 + "compositePrimaryKeys": {}, 372 + "uniqueConstraints": {}, 373 + "checkConstraints": {} 374 + }, 375 + "users_to_workspaces": { 376 + "name": "users_to_workspaces", 377 + "columns": { 378 + "user_id": { 379 + "name": "user_id", 380 + "type": "integer", 381 + "primaryKey": false, 382 + "notNull": true, 383 + "autoincrement": false 384 + }, 385 + "workspace_id": { 386 + "name": "workspace_id", 387 + "type": "integer", 388 + "primaryKey": false, 389 + "notNull": true, 390 + "autoincrement": false 391 + }, 392 + "role": { 393 + "name": "role", 394 + "type": "text", 395 + "primaryKey": false, 396 + "notNull": true, 397 + "autoincrement": false, 398 + "default": "'member'" 399 + }, 400 + "created_at": { 401 + "name": "created_at", 402 + "type": "integer", 403 + "primaryKey": false, 404 + "notNull": false, 405 + "autoincrement": false, 406 + "default": "(strftime('%s', 'now'))" 407 + } 408 + }, 409 + "indexes": {}, 410 + "foreignKeys": { 411 + "users_to_workspaces_user_id_user_id_fk": { 412 + "name": "users_to_workspaces_user_id_user_id_fk", 413 + "tableFrom": "users_to_workspaces", 414 + "tableTo": "user", 415 + "columnsFrom": [ 416 + "user_id" 417 + ], 418 + "columnsTo": [ 419 + "id" 420 + ], 421 + "onDelete": "no action", 422 + "onUpdate": "no action" 423 + }, 424 + "users_to_workspaces_workspace_id_workspace_id_fk": { 425 + "name": "users_to_workspaces_workspace_id_workspace_id_fk", 426 + "tableFrom": "users_to_workspaces", 427 + "tableTo": "workspace", 428 + "columnsFrom": [ 429 + "workspace_id" 430 + ], 431 + "columnsTo": [ 432 + "id" 433 + ], 434 + "onDelete": "no action", 435 + "onUpdate": "no action" 436 + } 437 + }, 438 + "compositePrimaryKeys": { 439 + "users_to_workspaces_user_id_workspace_id_pk": { 440 + "columns": [ 441 + "user_id", 442 + "workspace_id" 443 + ], 444 + "name": "users_to_workspaces_user_id_workspace_id_pk" 445 + } 446 + }, 447 + "uniqueConstraints": {}, 448 + "checkConstraints": {} 449 + }, 450 + "verification_token": { 451 + "name": "verification_token", 452 + "columns": { 453 + "identifier": { 454 + "name": "identifier", 455 + "type": "text", 456 + "primaryKey": false, 457 + "notNull": true, 458 + "autoincrement": false 459 + }, 460 + "token": { 461 + "name": "token", 462 + "type": "text", 463 + "primaryKey": false, 464 + "notNull": true, 465 + "autoincrement": false 466 + }, 467 + "expires": { 468 + "name": "expires", 469 + "type": "integer", 470 + "primaryKey": false, 471 + "notNull": true, 472 + "autoincrement": false 473 + } 474 + }, 475 + "indexes": {}, 476 + "foreignKeys": {}, 477 + "compositePrimaryKeys": { 478 + "verification_token_identifier_token_pk": { 479 + "columns": [ 480 + "identifier", 481 + "token" 482 + ], 483 + "name": "verification_token_identifier_token_pk" 484 + } 485 + }, 486 + "uniqueConstraints": {}, 487 + "checkConstraints": {} 488 + }, 489 + "status_report_to_monitors": { 490 + "name": "status_report_to_monitors", 491 + "columns": { 492 + "monitor_id": { 493 + "name": "monitor_id", 494 + "type": "integer", 495 + "primaryKey": false, 496 + "notNull": true, 497 + "autoincrement": false 498 + }, 499 + "status_report_id": { 500 + "name": "status_report_id", 501 + "type": "integer", 502 + "primaryKey": false, 503 + "notNull": true, 504 + "autoincrement": false 505 + }, 506 + "created_at": { 507 + "name": "created_at", 508 + "type": "integer", 509 + "primaryKey": false, 510 + "notNull": false, 511 + "autoincrement": false, 512 + "default": "(strftime('%s', 'now'))" 513 + } 514 + }, 515 + "indexes": {}, 516 + "foreignKeys": { 517 + "status_report_to_monitors_monitor_id_monitor_id_fk": { 518 + "name": "status_report_to_monitors_monitor_id_monitor_id_fk", 519 + "tableFrom": "status_report_to_monitors", 520 + "tableTo": "monitor", 521 + "columnsFrom": [ 522 + "monitor_id" 523 + ], 524 + "columnsTo": [ 525 + "id" 526 + ], 527 + "onDelete": "cascade", 528 + "onUpdate": "no action" 529 + }, 530 + "status_report_to_monitors_status_report_id_status_report_id_fk": { 531 + "name": "status_report_to_monitors_status_report_id_status_report_id_fk", 532 + "tableFrom": "status_report_to_monitors", 533 + "tableTo": "status_report", 534 + "columnsFrom": [ 535 + "status_report_id" 536 + ], 537 + "columnsTo": [ 538 + "id" 539 + ], 540 + "onDelete": "cascade", 541 + "onUpdate": "no action" 542 + } 543 + }, 544 + "compositePrimaryKeys": { 545 + "status_report_to_monitors_monitor_id_status_report_id_pk": { 546 + "columns": [ 547 + "monitor_id", 548 + "status_report_id" 549 + ], 550 + "name": "status_report_to_monitors_monitor_id_status_report_id_pk" 551 + } 552 + }, 553 + "uniqueConstraints": {}, 554 + "checkConstraints": {} 555 + }, 556 + "status_report": { 557 + "name": "status_report", 558 + "columns": { 559 + "id": { 560 + "name": "id", 561 + "type": "integer", 562 + "primaryKey": true, 563 + "notNull": true, 564 + "autoincrement": false 565 + }, 566 + "status": { 567 + "name": "status", 568 + "type": "text", 569 + "primaryKey": false, 570 + "notNull": true, 571 + "autoincrement": false 572 + }, 573 + "title": { 574 + "name": "title", 575 + "type": "text(256)", 576 + "primaryKey": false, 577 + "notNull": true, 578 + "autoincrement": false 579 + }, 580 + "workspace_id": { 581 + "name": "workspace_id", 582 + "type": "integer", 583 + "primaryKey": false, 584 + "notNull": false, 585 + "autoincrement": false 586 + }, 587 + "page_id": { 588 + "name": "page_id", 589 + "type": "integer", 590 + "primaryKey": false, 591 + "notNull": false, 592 + "autoincrement": false 593 + }, 594 + "created_at": { 595 + "name": "created_at", 596 + "type": "integer", 597 + "primaryKey": false, 598 + "notNull": false, 599 + "autoincrement": false, 600 + "default": "(strftime('%s', 'now'))" 601 + }, 602 + "updated_at": { 603 + "name": "updated_at", 604 + "type": "integer", 605 + "primaryKey": false, 606 + "notNull": false, 607 + "autoincrement": false, 608 + "default": "(strftime('%s', 'now'))" 609 + } 610 + }, 611 + "indexes": {}, 612 + "foreignKeys": { 613 + "status_report_workspace_id_workspace_id_fk": { 614 + "name": "status_report_workspace_id_workspace_id_fk", 615 + "tableFrom": "status_report", 616 + "tableTo": "workspace", 617 + "columnsFrom": [ 618 + "workspace_id" 619 + ], 620 + "columnsTo": [ 621 + "id" 622 + ], 623 + "onDelete": "no action", 624 + "onUpdate": "no action" 625 + }, 626 + "status_report_page_id_page_id_fk": { 627 + "name": "status_report_page_id_page_id_fk", 628 + "tableFrom": "status_report", 629 + "tableTo": "page", 630 + "columnsFrom": [ 631 + "page_id" 632 + ], 633 + "columnsTo": [ 634 + "id" 635 + ], 636 + "onDelete": "cascade", 637 + "onUpdate": "no action" 638 + } 639 + }, 640 + "compositePrimaryKeys": {}, 641 + "uniqueConstraints": {}, 642 + "checkConstraints": {} 643 + }, 644 + "status_report_update": { 645 + "name": "status_report_update", 646 + "columns": { 647 + "id": { 648 + "name": "id", 649 + "type": "integer", 650 + "primaryKey": true, 651 + "notNull": true, 652 + "autoincrement": false 653 + }, 654 + "status": { 655 + "name": "status", 656 + "type": "text", 657 + "primaryKey": false, 658 + "notNull": true, 659 + "autoincrement": false 660 + }, 661 + "date": { 662 + "name": "date", 663 + "type": "integer", 664 + "primaryKey": false, 665 + "notNull": true, 666 + "autoincrement": false 667 + }, 668 + "message": { 669 + "name": "message", 670 + "type": "text", 671 + "primaryKey": false, 672 + "notNull": true, 673 + "autoincrement": false 674 + }, 675 + "status_report_id": { 676 + "name": "status_report_id", 677 + "type": "integer", 678 + "primaryKey": false, 679 + "notNull": true, 680 + "autoincrement": false 681 + }, 682 + "created_at": { 683 + "name": "created_at", 684 + "type": "integer", 685 + "primaryKey": false, 686 + "notNull": false, 687 + "autoincrement": false, 688 + "default": "(strftime('%s', 'now'))" 689 + }, 690 + "updated_at": { 691 + "name": "updated_at", 692 + "type": "integer", 693 + "primaryKey": false, 694 + "notNull": false, 695 + "autoincrement": false, 696 + "default": "(strftime('%s', 'now'))" 697 + } 698 + }, 699 + "indexes": {}, 700 + "foreignKeys": { 701 + "status_report_update_status_report_id_status_report_id_fk": { 702 + "name": "status_report_update_status_report_id_status_report_id_fk", 703 + "tableFrom": "status_report_update", 704 + "tableTo": "status_report", 705 + "columnsFrom": [ 706 + "status_report_id" 707 + ], 708 + "columnsTo": [ 709 + "id" 710 + ], 711 + "onDelete": "cascade", 712 + "onUpdate": "no action" 713 + } 714 + }, 715 + "compositePrimaryKeys": {}, 716 + "uniqueConstraints": {}, 717 + "checkConstraints": {} 718 + }, 719 + "integration": { 720 + "name": "integration", 721 + "columns": { 722 + "id": { 723 + "name": "id", 724 + "type": "integer", 725 + "primaryKey": true, 726 + "notNull": true, 727 + "autoincrement": false 728 + }, 729 + "name": { 730 + "name": "name", 731 + "type": "text(256)", 732 + "primaryKey": false, 733 + "notNull": true, 734 + "autoincrement": false 735 + }, 736 + "workspace_id": { 737 + "name": "workspace_id", 738 + "type": "integer", 739 + "primaryKey": false, 740 + "notNull": false, 741 + "autoincrement": false 742 + }, 743 + "credential": { 744 + "name": "credential", 745 + "type": "text", 746 + "primaryKey": false, 747 + "notNull": false, 748 + "autoincrement": false 749 + }, 750 + "external_id": { 751 + "name": "external_id", 752 + "type": "text", 753 + "primaryKey": false, 754 + "notNull": true, 755 + "autoincrement": false 756 + }, 757 + "created_at": { 758 + "name": "created_at", 759 + "type": "integer", 760 + "primaryKey": false, 761 + "notNull": false, 762 + "autoincrement": false, 763 + "default": "(strftime('%s', 'now'))" 764 + }, 765 + "updated_at": { 766 + "name": "updated_at", 767 + "type": "integer", 768 + "primaryKey": false, 769 + "notNull": false, 770 + "autoincrement": false, 771 + "default": "(strftime('%s', 'now'))" 772 + }, 773 + "data": { 774 + "name": "data", 775 + "type": "text", 776 + "primaryKey": false, 777 + "notNull": true, 778 + "autoincrement": false 779 + } 780 + }, 781 + "indexes": {}, 782 + "foreignKeys": { 783 + "integration_workspace_id_workspace_id_fk": { 784 + "name": "integration_workspace_id_workspace_id_fk", 785 + "tableFrom": "integration", 786 + "tableTo": "workspace", 787 + "columnsFrom": [ 788 + "workspace_id" 789 + ], 790 + "columnsTo": [ 791 + "id" 792 + ], 793 + "onDelete": "no action", 794 + "onUpdate": "no action" 795 + } 796 + }, 797 + "compositePrimaryKeys": {}, 798 + "uniqueConstraints": {}, 799 + "checkConstraints": {} 800 + }, 801 + "page": { 802 + "name": "page", 803 + "columns": { 804 + "id": { 805 + "name": "id", 806 + "type": "integer", 807 + "primaryKey": true, 808 + "notNull": true, 809 + "autoincrement": false 810 + }, 811 + "workspace_id": { 812 + "name": "workspace_id", 813 + "type": "integer", 814 + "primaryKey": false, 815 + "notNull": true, 816 + "autoincrement": false 817 + }, 818 + "title": { 819 + "name": "title", 820 + "type": "text", 821 + "primaryKey": false, 822 + "notNull": true, 823 + "autoincrement": false 824 + }, 825 + "description": { 826 + "name": "description", 827 + "type": "text", 828 + "primaryKey": false, 829 + "notNull": true, 830 + "autoincrement": false 831 + }, 832 + "icon": { 833 + "name": "icon", 834 + "type": "text(256)", 835 + "primaryKey": false, 836 + "notNull": false, 837 + "autoincrement": false, 838 + "default": "''" 839 + }, 840 + "slug": { 841 + "name": "slug", 842 + "type": "text(256)", 843 + "primaryKey": false, 844 + "notNull": true, 845 + "autoincrement": false 846 + }, 847 + "custom_domain": { 848 + "name": "custom_domain", 849 + "type": "text(256)", 850 + "primaryKey": false, 851 + "notNull": true, 852 + "autoincrement": false 853 + }, 854 + "published": { 855 + "name": "published", 856 + "type": "integer", 857 + "primaryKey": false, 858 + "notNull": false, 859 + "autoincrement": false, 860 + "default": false 861 + }, 862 + "force_theme": { 863 + "name": "force_theme", 864 + "type": "text", 865 + "primaryKey": false, 866 + "notNull": true, 867 + "autoincrement": false, 868 + "default": "'system'" 869 + }, 870 + "password": { 871 + "name": "password", 872 + "type": "text(256)", 873 + "primaryKey": false, 874 + "notNull": false, 875 + "autoincrement": false 876 + }, 877 + "password_protected": { 878 + "name": "password_protected", 879 + "type": "integer", 880 + "primaryKey": false, 881 + "notNull": false, 882 + "autoincrement": false, 883 + "default": false 884 + }, 885 + "access_type": { 886 + "name": "access_type", 887 + "type": "text", 888 + "primaryKey": false, 889 + "notNull": false, 890 + "autoincrement": false, 891 + "default": "'public'" 892 + }, 893 + "auth_email_domains": { 894 + "name": "auth_email_domains", 895 + "type": "text", 896 + "primaryKey": false, 897 + "notNull": false, 898 + "autoincrement": false 899 + }, 900 + "homepage_url": { 901 + "name": "homepage_url", 902 + "type": "text(256)", 903 + "primaryKey": false, 904 + "notNull": false, 905 + "autoincrement": false 906 + }, 907 + "contact_url": { 908 + "name": "contact_url", 909 + "type": "text(256)", 910 + "primaryKey": false, 911 + "notNull": false, 912 + "autoincrement": false 913 + }, 914 + "legacy_page": { 915 + "name": "legacy_page", 916 + "type": "integer", 917 + "primaryKey": false, 918 + "notNull": true, 919 + "autoincrement": false, 920 + "default": true 921 + }, 922 + "configuration": { 923 + "name": "configuration", 924 + "type": "text", 925 + "primaryKey": false, 926 + "notNull": false, 927 + "autoincrement": false 928 + }, 929 + "show_monitor_values": { 930 + "name": "show_monitor_values", 931 + "type": "integer", 932 + "primaryKey": false, 933 + "notNull": false, 934 + "autoincrement": false, 935 + "default": true 936 + }, 937 + "created_at": { 938 + "name": "created_at", 939 + "type": "integer", 940 + "primaryKey": false, 941 + "notNull": false, 942 + "autoincrement": false, 943 + "default": "(strftime('%s', 'now'))" 944 + }, 945 + "updated_at": { 946 + "name": "updated_at", 947 + "type": "integer", 948 + "primaryKey": false, 949 + "notNull": false, 950 + "autoincrement": false, 951 + "default": "(strftime('%s', 'now'))" 952 + } 953 + }, 954 + "indexes": { 955 + "page_slug_unique": { 956 + "name": "page_slug_unique", 957 + "columns": [ 958 + "slug" 959 + ], 960 + "isUnique": true 961 + } 962 + }, 963 + "foreignKeys": { 964 + "page_workspace_id_workspace_id_fk": { 965 + "name": "page_workspace_id_workspace_id_fk", 966 + "tableFrom": "page", 967 + "tableTo": "workspace", 968 + "columnsFrom": [ 969 + "workspace_id" 970 + ], 971 + "columnsTo": [ 972 + "id" 973 + ], 974 + "onDelete": "cascade", 975 + "onUpdate": "no action" 976 + } 977 + }, 978 + "compositePrimaryKeys": {}, 979 + "uniqueConstraints": {}, 980 + "checkConstraints": {} 981 + }, 982 + "monitor": { 983 + "name": "monitor", 984 + "columns": { 985 + "id": { 986 + "name": "id", 987 + "type": "integer", 988 + "primaryKey": true, 989 + "notNull": true, 990 + "autoincrement": false 991 + }, 992 + "job_type": { 993 + "name": "job_type", 994 + "type": "text", 995 + "primaryKey": false, 996 + "notNull": true, 997 + "autoincrement": false, 998 + "default": "'http'" 999 + }, 1000 + "periodicity": { 1001 + "name": "periodicity", 1002 + "type": "text", 1003 + "primaryKey": false, 1004 + "notNull": true, 1005 + "autoincrement": false, 1006 + "default": "'other'" 1007 + }, 1008 + "status": { 1009 + "name": "status", 1010 + "type": "text", 1011 + "primaryKey": false, 1012 + "notNull": true, 1013 + "autoincrement": false, 1014 + "default": "'active'" 1015 + }, 1016 + "active": { 1017 + "name": "active", 1018 + "type": "integer", 1019 + "primaryKey": false, 1020 + "notNull": false, 1021 + "autoincrement": false, 1022 + "default": false 1023 + }, 1024 + "regions": { 1025 + "name": "regions", 1026 + "type": "text", 1027 + "primaryKey": false, 1028 + "notNull": true, 1029 + "autoincrement": false, 1030 + "default": "''" 1031 + }, 1032 + "url": { 1033 + "name": "url", 1034 + "type": "text(2048)", 1035 + "primaryKey": false, 1036 + "notNull": true, 1037 + "autoincrement": false 1038 + }, 1039 + "name": { 1040 + "name": "name", 1041 + "type": "text(256)", 1042 + "primaryKey": false, 1043 + "notNull": true, 1044 + "autoincrement": false, 1045 + "default": "''" 1046 + }, 1047 + "external_name": { 1048 + "name": "external_name", 1049 + "type": "text", 1050 + "primaryKey": false, 1051 + "notNull": false, 1052 + "autoincrement": false 1053 + }, 1054 + "description": { 1055 + "name": "description", 1056 + "type": "text", 1057 + "primaryKey": false, 1058 + "notNull": true, 1059 + "autoincrement": false, 1060 + "default": "''" 1061 + }, 1062 + "headers": { 1063 + "name": "headers", 1064 + "type": "text", 1065 + "primaryKey": false, 1066 + "notNull": false, 1067 + "autoincrement": false, 1068 + "default": "''" 1069 + }, 1070 + "body": { 1071 + "name": "body", 1072 + "type": "text", 1073 + "primaryKey": false, 1074 + "notNull": false, 1075 + "autoincrement": false, 1076 + "default": "''" 1077 + }, 1078 + "method": { 1079 + "name": "method", 1080 + "type": "text", 1081 + "primaryKey": false, 1082 + "notNull": false, 1083 + "autoincrement": false, 1084 + "default": "'GET'" 1085 + }, 1086 + "workspace_id": { 1087 + "name": "workspace_id", 1088 + "type": "integer", 1089 + "primaryKey": false, 1090 + "notNull": false, 1091 + "autoincrement": false 1092 + }, 1093 + "timeout": { 1094 + "name": "timeout", 1095 + "type": "integer", 1096 + "primaryKey": false, 1097 + "notNull": true, 1098 + "autoincrement": false, 1099 + "default": 45000 1100 + }, 1101 + "degraded_after": { 1102 + "name": "degraded_after", 1103 + "type": "integer", 1104 + "primaryKey": false, 1105 + "notNull": false, 1106 + "autoincrement": false 1107 + }, 1108 + "assertions": { 1109 + "name": "assertions", 1110 + "type": "text", 1111 + "primaryKey": false, 1112 + "notNull": false, 1113 + "autoincrement": false 1114 + }, 1115 + "otel_endpoint": { 1116 + "name": "otel_endpoint", 1117 + "type": "text", 1118 + "primaryKey": false, 1119 + "notNull": false, 1120 + "autoincrement": false 1121 + }, 1122 + "otel_headers": { 1123 + "name": "otel_headers", 1124 + "type": "text", 1125 + "primaryKey": false, 1126 + "notNull": false, 1127 + "autoincrement": false 1128 + }, 1129 + "public": { 1130 + "name": "public", 1131 + "type": "integer", 1132 + "primaryKey": false, 1133 + "notNull": false, 1134 + "autoincrement": false, 1135 + "default": false 1136 + }, 1137 + "retry": { 1138 + "name": "retry", 1139 + "type": "integer", 1140 + "primaryKey": false, 1141 + "notNull": false, 1142 + "autoincrement": false, 1143 + "default": 3 1144 + }, 1145 + "follow_redirects": { 1146 + "name": "follow_redirects", 1147 + "type": "integer", 1148 + "primaryKey": false, 1149 + "notNull": false, 1150 + "autoincrement": false, 1151 + "default": true 1152 + }, 1153 + "created_at": { 1154 + "name": "created_at", 1155 + "type": "integer", 1156 + "primaryKey": false, 1157 + "notNull": false, 1158 + "autoincrement": false, 1159 + "default": "(strftime('%s', 'now'))" 1160 + }, 1161 + "updated_at": { 1162 + "name": "updated_at", 1163 + "type": "integer", 1164 + "primaryKey": false, 1165 + "notNull": false, 1166 + "autoincrement": false, 1167 + "default": "(strftime('%s', 'now'))" 1168 + }, 1169 + "deleted_at": { 1170 + "name": "deleted_at", 1171 + "type": "integer", 1172 + "primaryKey": false, 1173 + "notNull": false, 1174 + "autoincrement": false 1175 + } 1176 + }, 1177 + "indexes": {}, 1178 + "foreignKeys": { 1179 + "monitor_workspace_id_workspace_id_fk": { 1180 + "name": "monitor_workspace_id_workspace_id_fk", 1181 + "tableFrom": "monitor", 1182 + "tableTo": "workspace", 1183 + "columnsFrom": [ 1184 + "workspace_id" 1185 + ], 1186 + "columnsTo": [ 1187 + "id" 1188 + ], 1189 + "onDelete": "no action", 1190 + "onUpdate": "no action" 1191 + } 1192 + }, 1193 + "compositePrimaryKeys": {}, 1194 + "uniqueConstraints": {}, 1195 + "checkConstraints": {} 1196 + }, 1197 + "monitors_to_pages": { 1198 + "name": "monitors_to_pages", 1199 + "columns": { 1200 + "monitor_id": { 1201 + "name": "monitor_id", 1202 + "type": "integer", 1203 + "primaryKey": false, 1204 + "notNull": true, 1205 + "autoincrement": false 1206 + }, 1207 + "page_id": { 1208 + "name": "page_id", 1209 + "type": "integer", 1210 + "primaryKey": false, 1211 + "notNull": true, 1212 + "autoincrement": false 1213 + }, 1214 + "created_at": { 1215 + "name": "created_at", 1216 + "type": "integer", 1217 + "primaryKey": false, 1218 + "notNull": false, 1219 + "autoincrement": false, 1220 + "default": "(strftime('%s', 'now'))" 1221 + }, 1222 + "order": { 1223 + "name": "order", 1224 + "type": "integer", 1225 + "primaryKey": false, 1226 + "notNull": false, 1227 + "autoincrement": false, 1228 + "default": 0 1229 + }, 1230 + "monitor_group_id": { 1231 + "name": "monitor_group_id", 1232 + "type": "integer", 1233 + "primaryKey": false, 1234 + "notNull": false, 1235 + "autoincrement": false 1236 + }, 1237 + "group_order": { 1238 + "name": "group_order", 1239 + "type": "integer", 1240 + "primaryKey": false, 1241 + "notNull": false, 1242 + "autoincrement": false, 1243 + "default": 0 1244 + } 1245 + }, 1246 + "indexes": {}, 1247 + "foreignKeys": { 1248 + "monitors_to_pages_monitor_id_monitor_id_fk": { 1249 + "name": "monitors_to_pages_monitor_id_monitor_id_fk", 1250 + "tableFrom": "monitors_to_pages", 1251 + "tableTo": "monitor", 1252 + "columnsFrom": [ 1253 + "monitor_id" 1254 + ], 1255 + "columnsTo": [ 1256 + "id" 1257 + ], 1258 + "onDelete": "cascade", 1259 + "onUpdate": "no action" 1260 + }, 1261 + "monitors_to_pages_page_id_page_id_fk": { 1262 + "name": "monitors_to_pages_page_id_page_id_fk", 1263 + "tableFrom": "monitors_to_pages", 1264 + "tableTo": "page", 1265 + "columnsFrom": [ 1266 + "page_id" 1267 + ], 1268 + "columnsTo": [ 1269 + "id" 1270 + ], 1271 + "onDelete": "cascade", 1272 + "onUpdate": "no action" 1273 + }, 1274 + "monitors_to_pages_monitor_group_id_monitor_group_id_fk": { 1275 + "name": "monitors_to_pages_monitor_group_id_monitor_group_id_fk", 1276 + "tableFrom": "monitors_to_pages", 1277 + "tableTo": "monitor_group", 1278 + "columnsFrom": [ 1279 + "monitor_group_id" 1280 + ], 1281 + "columnsTo": [ 1282 + "id" 1283 + ], 1284 + "onDelete": "cascade", 1285 + "onUpdate": "no action" 1286 + } 1287 + }, 1288 + "compositePrimaryKeys": { 1289 + "monitors_to_pages_monitor_id_page_id_pk": { 1290 + "columns": [ 1291 + "monitor_id", 1292 + "page_id" 1293 + ], 1294 + "name": "monitors_to_pages_monitor_id_page_id_pk" 1295 + } 1296 + }, 1297 + "uniqueConstraints": {}, 1298 + "checkConstraints": {} 1299 + }, 1300 + "page_subscriber": { 1301 + "name": "page_subscriber", 1302 + "columns": { 1303 + "id": { 1304 + "name": "id", 1305 + "type": "integer", 1306 + "primaryKey": true, 1307 + "notNull": true, 1308 + "autoincrement": false 1309 + }, 1310 + "email": { 1311 + "name": "email", 1312 + "type": "text", 1313 + "primaryKey": false, 1314 + "notNull": true, 1315 + "autoincrement": false 1316 + }, 1317 + "page_id": { 1318 + "name": "page_id", 1319 + "type": "integer", 1320 + "primaryKey": false, 1321 + "notNull": true, 1322 + "autoincrement": false 1323 + }, 1324 + "token": { 1325 + "name": "token", 1326 + "type": "text", 1327 + "primaryKey": false, 1328 + "notNull": false, 1329 + "autoincrement": false 1330 + }, 1331 + "accepted_at": { 1332 + "name": "accepted_at", 1333 + "type": "integer", 1334 + "primaryKey": false, 1335 + "notNull": false, 1336 + "autoincrement": false 1337 + }, 1338 + "expires_at": { 1339 + "name": "expires_at", 1340 + "type": "integer", 1341 + "primaryKey": false, 1342 + "notNull": false, 1343 + "autoincrement": false 1344 + }, 1345 + "unsubscribed_at": { 1346 + "name": "unsubscribed_at", 1347 + "type": "integer", 1348 + "primaryKey": false, 1349 + "notNull": false, 1350 + "autoincrement": false 1351 + }, 1352 + "created_at": { 1353 + "name": "created_at", 1354 + "type": "integer", 1355 + "primaryKey": false, 1356 + "notNull": false, 1357 + "autoincrement": false, 1358 + "default": "(strftime('%s', 'now'))" 1359 + }, 1360 + "updated_at": { 1361 + "name": "updated_at", 1362 + "type": "integer", 1363 + "primaryKey": false, 1364 + "notNull": false, 1365 + "autoincrement": false, 1366 + "default": "(strftime('%s', 'now'))" 1367 + } 1368 + }, 1369 + "indexes": {}, 1370 + "foreignKeys": { 1371 + "page_subscriber_page_id_page_id_fk": { 1372 + "name": "page_subscriber_page_id_page_id_fk", 1373 + "tableFrom": "page_subscriber", 1374 + "tableTo": "page", 1375 + "columnsFrom": [ 1376 + "page_id" 1377 + ], 1378 + "columnsTo": [ 1379 + "id" 1380 + ], 1381 + "onDelete": "cascade", 1382 + "onUpdate": "no action" 1383 + } 1384 + }, 1385 + "compositePrimaryKeys": {}, 1386 + "uniqueConstraints": {}, 1387 + "checkConstraints": {} 1388 + }, 1389 + "notification": { 1390 + "name": "notification", 1391 + "columns": { 1392 + "id": { 1393 + "name": "id", 1394 + "type": "integer", 1395 + "primaryKey": true, 1396 + "notNull": true, 1397 + "autoincrement": false 1398 + }, 1399 + "name": { 1400 + "name": "name", 1401 + "type": "text", 1402 + "primaryKey": false, 1403 + "notNull": true, 1404 + "autoincrement": false 1405 + }, 1406 + "provider": { 1407 + "name": "provider", 1408 + "type": "text", 1409 + "primaryKey": false, 1410 + "notNull": true, 1411 + "autoincrement": false 1412 + }, 1413 + "data": { 1414 + "name": "data", 1415 + "type": "text", 1416 + "primaryKey": false, 1417 + "notNull": false, 1418 + "autoincrement": false, 1419 + "default": "'{}'" 1420 + }, 1421 + "workspace_id": { 1422 + "name": "workspace_id", 1423 + "type": "integer", 1424 + "primaryKey": false, 1425 + "notNull": false, 1426 + "autoincrement": false 1427 + }, 1428 + "created_at": { 1429 + "name": "created_at", 1430 + "type": "integer", 1431 + "primaryKey": false, 1432 + "notNull": false, 1433 + "autoincrement": false, 1434 + "default": "(strftime('%s', 'now'))" 1435 + }, 1436 + "updated_at": { 1437 + "name": "updated_at", 1438 + "type": "integer", 1439 + "primaryKey": false, 1440 + "notNull": false, 1441 + "autoincrement": false, 1442 + "default": "(strftime('%s', 'now'))" 1443 + } 1444 + }, 1445 + "indexes": {}, 1446 + "foreignKeys": { 1447 + "notification_workspace_id_workspace_id_fk": { 1448 + "name": "notification_workspace_id_workspace_id_fk", 1449 + "tableFrom": "notification", 1450 + "tableTo": "workspace", 1451 + "columnsFrom": [ 1452 + "workspace_id" 1453 + ], 1454 + "columnsTo": [ 1455 + "id" 1456 + ], 1457 + "onDelete": "no action", 1458 + "onUpdate": "no action" 1459 + } 1460 + }, 1461 + "compositePrimaryKeys": {}, 1462 + "uniqueConstraints": {}, 1463 + "checkConstraints": {} 1464 + }, 1465 + "notification_trigger": { 1466 + "name": "notification_trigger", 1467 + "columns": { 1468 + "id": { 1469 + "name": "id", 1470 + "type": "integer", 1471 + "primaryKey": true, 1472 + "notNull": true, 1473 + "autoincrement": false 1474 + }, 1475 + "monitor_id": { 1476 + "name": "monitor_id", 1477 + "type": "integer", 1478 + "primaryKey": false, 1479 + "notNull": false, 1480 + "autoincrement": false 1481 + }, 1482 + "notification_id": { 1483 + "name": "notification_id", 1484 + "type": "integer", 1485 + "primaryKey": false, 1486 + "notNull": false, 1487 + "autoincrement": false 1488 + }, 1489 + "cron_timestamp": { 1490 + "name": "cron_timestamp", 1491 + "type": "integer", 1492 + "primaryKey": false, 1493 + "notNull": true, 1494 + "autoincrement": false 1495 + } 1496 + }, 1497 + "indexes": { 1498 + "notification_id_monitor_id_crontimestampe": { 1499 + "name": "notification_id_monitor_id_crontimestampe", 1500 + "columns": [ 1501 + "notification_id", 1502 + "monitor_id", 1503 + "cron_timestamp" 1504 + ], 1505 + "isUnique": true 1506 + } 1507 + }, 1508 + "foreignKeys": { 1509 + "notification_trigger_monitor_id_monitor_id_fk": { 1510 + "name": "notification_trigger_monitor_id_monitor_id_fk", 1511 + "tableFrom": "notification_trigger", 1512 + "tableTo": "monitor", 1513 + "columnsFrom": [ 1514 + "monitor_id" 1515 + ], 1516 + "columnsTo": [ 1517 + "id" 1518 + ], 1519 + "onDelete": "cascade", 1520 + "onUpdate": "no action" 1521 + }, 1522 + "notification_trigger_notification_id_notification_id_fk": { 1523 + "name": "notification_trigger_notification_id_notification_id_fk", 1524 + "tableFrom": "notification_trigger", 1525 + "tableTo": "notification", 1526 + "columnsFrom": [ 1527 + "notification_id" 1528 + ], 1529 + "columnsTo": [ 1530 + "id" 1531 + ], 1532 + "onDelete": "cascade", 1533 + "onUpdate": "no action" 1534 + } 1535 + }, 1536 + "compositePrimaryKeys": {}, 1537 + "uniqueConstraints": {}, 1538 + "checkConstraints": {} 1539 + }, 1540 + "notifications_to_monitors": { 1541 + "name": "notifications_to_monitors", 1542 + "columns": { 1543 + "monitor_id": { 1544 + "name": "monitor_id", 1545 + "type": "integer", 1546 + "primaryKey": false, 1547 + "notNull": true, 1548 + "autoincrement": false 1549 + }, 1550 + "notification_id": { 1551 + "name": "notification_id", 1552 + "type": "integer", 1553 + "primaryKey": false, 1554 + "notNull": true, 1555 + "autoincrement": false 1556 + }, 1557 + "created_at": { 1558 + "name": "created_at", 1559 + "type": "integer", 1560 + "primaryKey": false, 1561 + "notNull": false, 1562 + "autoincrement": false, 1563 + "default": "(strftime('%s', 'now'))" 1564 + } 1565 + }, 1566 + "indexes": {}, 1567 + "foreignKeys": { 1568 + "notifications_to_monitors_monitor_id_monitor_id_fk": { 1569 + "name": "notifications_to_monitors_monitor_id_monitor_id_fk", 1570 + "tableFrom": "notifications_to_monitors", 1571 + "tableTo": "monitor", 1572 + "columnsFrom": [ 1573 + "monitor_id" 1574 + ], 1575 + "columnsTo": [ 1576 + "id" 1577 + ], 1578 + "onDelete": "cascade", 1579 + "onUpdate": "no action" 1580 + }, 1581 + "notifications_to_monitors_notification_id_notification_id_fk": { 1582 + "name": "notifications_to_monitors_notification_id_notification_id_fk", 1583 + "tableFrom": "notifications_to_monitors", 1584 + "tableTo": "notification", 1585 + "columnsFrom": [ 1586 + "notification_id" 1587 + ], 1588 + "columnsTo": [ 1589 + "id" 1590 + ], 1591 + "onDelete": "cascade", 1592 + "onUpdate": "no action" 1593 + } 1594 + }, 1595 + "compositePrimaryKeys": { 1596 + "notifications_to_monitors_monitor_id_notification_id_pk": { 1597 + "columns": [ 1598 + "monitor_id", 1599 + "notification_id" 1600 + ], 1601 + "name": "notifications_to_monitors_monitor_id_notification_id_pk" 1602 + } 1603 + }, 1604 + "uniqueConstraints": {}, 1605 + "checkConstraints": {} 1606 + }, 1607 + "monitor_status": { 1608 + "name": "monitor_status", 1609 + "columns": { 1610 + "monitor_id": { 1611 + "name": "monitor_id", 1612 + "type": "integer", 1613 + "primaryKey": false, 1614 + "notNull": true, 1615 + "autoincrement": false 1616 + }, 1617 + "region": { 1618 + "name": "region", 1619 + "type": "text", 1620 + "primaryKey": false, 1621 + "notNull": true, 1622 + "autoincrement": false, 1623 + "default": "''" 1624 + }, 1625 + "status": { 1626 + "name": "status", 1627 + "type": "text", 1628 + "primaryKey": false, 1629 + "notNull": true, 1630 + "autoincrement": false, 1631 + "default": "'active'" 1632 + }, 1633 + "created_at": { 1634 + "name": "created_at", 1635 + "type": "integer", 1636 + "primaryKey": false, 1637 + "notNull": false, 1638 + "autoincrement": false, 1639 + "default": "(strftime('%s', 'now'))" 1640 + }, 1641 + "updated_at": { 1642 + "name": "updated_at", 1643 + "type": "integer", 1644 + "primaryKey": false, 1645 + "notNull": false, 1646 + "autoincrement": false, 1647 + "default": "(strftime('%s', 'now'))" 1648 + } 1649 + }, 1650 + "indexes": { 1651 + "monitor_status_idx": { 1652 + "name": "monitor_status_idx", 1653 + "columns": [ 1654 + "monitor_id", 1655 + "region" 1656 + ], 1657 + "isUnique": false 1658 + } 1659 + }, 1660 + "foreignKeys": { 1661 + "monitor_status_monitor_id_monitor_id_fk": { 1662 + "name": "monitor_status_monitor_id_monitor_id_fk", 1663 + "tableFrom": "monitor_status", 1664 + "tableTo": "monitor", 1665 + "columnsFrom": [ 1666 + "monitor_id" 1667 + ], 1668 + "columnsTo": [ 1669 + "id" 1670 + ], 1671 + "onDelete": "cascade", 1672 + "onUpdate": "no action" 1673 + } 1674 + }, 1675 + "compositePrimaryKeys": { 1676 + "monitor_status_monitor_id_region_pk": { 1677 + "columns": [ 1678 + "monitor_id", 1679 + "region" 1680 + ], 1681 + "name": "monitor_status_monitor_id_region_pk" 1682 + } 1683 + }, 1684 + "uniqueConstraints": {}, 1685 + "checkConstraints": {} 1686 + }, 1687 + "invitation": { 1688 + "name": "invitation", 1689 + "columns": { 1690 + "id": { 1691 + "name": "id", 1692 + "type": "integer", 1693 + "primaryKey": true, 1694 + "notNull": true, 1695 + "autoincrement": false 1696 + }, 1697 + "email": { 1698 + "name": "email", 1699 + "type": "text", 1700 + "primaryKey": false, 1701 + "notNull": true, 1702 + "autoincrement": false 1703 + }, 1704 + "role": { 1705 + "name": "role", 1706 + "type": "text", 1707 + "primaryKey": false, 1708 + "notNull": true, 1709 + "autoincrement": false, 1710 + "default": "'member'" 1711 + }, 1712 + "workspace_id": { 1713 + "name": "workspace_id", 1714 + "type": "integer", 1715 + "primaryKey": false, 1716 + "notNull": true, 1717 + "autoincrement": false 1718 + }, 1719 + "token": { 1720 + "name": "token", 1721 + "type": "text", 1722 + "primaryKey": false, 1723 + "notNull": true, 1724 + "autoincrement": false 1725 + }, 1726 + "expires_at": { 1727 + "name": "expires_at", 1728 + "type": "integer", 1729 + "primaryKey": false, 1730 + "notNull": true, 1731 + "autoincrement": false 1732 + }, 1733 + "created_at": { 1734 + "name": "created_at", 1735 + "type": "integer", 1736 + "primaryKey": false, 1737 + "notNull": false, 1738 + "autoincrement": false, 1739 + "default": "(strftime('%s', 'now'))" 1740 + }, 1741 + "accepted_at": { 1742 + "name": "accepted_at", 1743 + "type": "integer", 1744 + "primaryKey": false, 1745 + "notNull": false, 1746 + "autoincrement": false 1747 + } 1748 + }, 1749 + "indexes": {}, 1750 + "foreignKeys": {}, 1751 + "compositePrimaryKeys": {}, 1752 + "uniqueConstraints": {}, 1753 + "checkConstraints": {} 1754 + }, 1755 + "incident": { 1756 + "name": "incident", 1757 + "columns": { 1758 + "id": { 1759 + "name": "id", 1760 + "type": "integer", 1761 + "primaryKey": true, 1762 + "notNull": true, 1763 + "autoincrement": false 1764 + }, 1765 + "title": { 1766 + "name": "title", 1767 + "type": "text", 1768 + "primaryKey": false, 1769 + "notNull": true, 1770 + "autoincrement": false, 1771 + "default": "''" 1772 + }, 1773 + "summary": { 1774 + "name": "summary", 1775 + "type": "text", 1776 + "primaryKey": false, 1777 + "notNull": true, 1778 + "autoincrement": false, 1779 + "default": "''" 1780 + }, 1781 + "status": { 1782 + "name": "status", 1783 + "type": "text", 1784 + "primaryKey": false, 1785 + "notNull": true, 1786 + "autoincrement": false, 1787 + "default": "'triage'" 1788 + }, 1789 + "monitor_id": { 1790 + "name": "monitor_id", 1791 + "type": "integer", 1792 + "primaryKey": false, 1793 + "notNull": false, 1794 + "autoincrement": false 1795 + }, 1796 + "workspace_id": { 1797 + "name": "workspace_id", 1798 + "type": "integer", 1799 + "primaryKey": false, 1800 + "notNull": false, 1801 + "autoincrement": false 1802 + }, 1803 + "started_at": { 1804 + "name": "started_at", 1805 + "type": "integer", 1806 + "primaryKey": false, 1807 + "notNull": true, 1808 + "autoincrement": false, 1809 + "default": "(strftime('%s', 'now'))" 1810 + }, 1811 + "acknowledged_at": { 1812 + "name": "acknowledged_at", 1813 + "type": "integer", 1814 + "primaryKey": false, 1815 + "notNull": false, 1816 + "autoincrement": false 1817 + }, 1818 + "acknowledged_by": { 1819 + "name": "acknowledged_by", 1820 + "type": "integer", 1821 + "primaryKey": false, 1822 + "notNull": false, 1823 + "autoincrement": false 1824 + }, 1825 + "resolved_at": { 1826 + "name": "resolved_at", 1827 + "type": "integer", 1828 + "primaryKey": false, 1829 + "notNull": false, 1830 + "autoincrement": false 1831 + }, 1832 + "resolved_by": { 1833 + "name": "resolved_by", 1834 + "type": "integer", 1835 + "primaryKey": false, 1836 + "notNull": false, 1837 + "autoincrement": false 1838 + }, 1839 + "incident_screenshot_url": { 1840 + "name": "incident_screenshot_url", 1841 + "type": "text", 1842 + "primaryKey": false, 1843 + "notNull": false, 1844 + "autoincrement": false 1845 + }, 1846 + "recovery_screenshot_url": { 1847 + "name": "recovery_screenshot_url", 1848 + "type": "text", 1849 + "primaryKey": false, 1850 + "notNull": false, 1851 + "autoincrement": false 1852 + }, 1853 + "auto_resolved": { 1854 + "name": "auto_resolved", 1855 + "type": "integer", 1856 + "primaryKey": false, 1857 + "notNull": false, 1858 + "autoincrement": false, 1859 + "default": false 1860 + }, 1861 + "created_at": { 1862 + "name": "created_at", 1863 + "type": "integer", 1864 + "primaryKey": false, 1865 + "notNull": false, 1866 + "autoincrement": false, 1867 + "default": "(strftime('%s', 'now'))" 1868 + }, 1869 + "updated_at": { 1870 + "name": "updated_at", 1871 + "type": "integer", 1872 + "primaryKey": false, 1873 + "notNull": false, 1874 + "autoincrement": false, 1875 + "default": "(strftime('%s', 'now'))" 1876 + } 1877 + }, 1878 + "indexes": { 1879 + "incident_monitor_id_started_at_unique": { 1880 + "name": "incident_monitor_id_started_at_unique", 1881 + "columns": [ 1882 + "monitor_id", 1883 + "started_at" 1884 + ], 1885 + "isUnique": true 1886 + } 1887 + }, 1888 + "foreignKeys": { 1889 + "incident_monitor_id_monitor_id_fk": { 1890 + "name": "incident_monitor_id_monitor_id_fk", 1891 + "tableFrom": "incident", 1892 + "tableTo": "monitor", 1893 + "columnsFrom": [ 1894 + "monitor_id" 1895 + ], 1896 + "columnsTo": [ 1897 + "id" 1898 + ], 1899 + "onDelete": "set default", 1900 + "onUpdate": "no action" 1901 + }, 1902 + "incident_workspace_id_workspace_id_fk": { 1903 + "name": "incident_workspace_id_workspace_id_fk", 1904 + "tableFrom": "incident", 1905 + "tableTo": "workspace", 1906 + "columnsFrom": [ 1907 + "workspace_id" 1908 + ], 1909 + "columnsTo": [ 1910 + "id" 1911 + ], 1912 + "onDelete": "no action", 1913 + "onUpdate": "no action" 1914 + }, 1915 + "incident_acknowledged_by_user_id_fk": { 1916 + "name": "incident_acknowledged_by_user_id_fk", 1917 + "tableFrom": "incident", 1918 + "tableTo": "user", 1919 + "columnsFrom": [ 1920 + "acknowledged_by" 1921 + ], 1922 + "columnsTo": [ 1923 + "id" 1924 + ], 1925 + "onDelete": "no action", 1926 + "onUpdate": "no action" 1927 + }, 1928 + "incident_resolved_by_user_id_fk": { 1929 + "name": "incident_resolved_by_user_id_fk", 1930 + "tableFrom": "incident", 1931 + "tableTo": "user", 1932 + "columnsFrom": [ 1933 + "resolved_by" 1934 + ], 1935 + "columnsTo": [ 1936 + "id" 1937 + ], 1938 + "onDelete": "no action", 1939 + "onUpdate": "no action" 1940 + } 1941 + }, 1942 + "compositePrimaryKeys": {}, 1943 + "uniqueConstraints": {}, 1944 + "checkConstraints": {} 1945 + }, 1946 + "monitor_tag": { 1947 + "name": "monitor_tag", 1948 + "columns": { 1949 + "id": { 1950 + "name": "id", 1951 + "type": "integer", 1952 + "primaryKey": true, 1953 + "notNull": true, 1954 + "autoincrement": false 1955 + }, 1956 + "workspace_id": { 1957 + "name": "workspace_id", 1958 + "type": "integer", 1959 + "primaryKey": false, 1960 + "notNull": true, 1961 + "autoincrement": false 1962 + }, 1963 + "name": { 1964 + "name": "name", 1965 + "type": "text", 1966 + "primaryKey": false, 1967 + "notNull": true, 1968 + "autoincrement": false 1969 + }, 1970 + "color": { 1971 + "name": "color", 1972 + "type": "text", 1973 + "primaryKey": false, 1974 + "notNull": true, 1975 + "autoincrement": false 1976 + }, 1977 + "created_at": { 1978 + "name": "created_at", 1979 + "type": "integer", 1980 + "primaryKey": false, 1981 + "notNull": false, 1982 + "autoincrement": false, 1983 + "default": "(strftime('%s', 'now'))" 1984 + }, 1985 + "updated_at": { 1986 + "name": "updated_at", 1987 + "type": "integer", 1988 + "primaryKey": false, 1989 + "notNull": false, 1990 + "autoincrement": false, 1991 + "default": "(strftime('%s', 'now'))" 1992 + } 1993 + }, 1994 + "indexes": {}, 1995 + "foreignKeys": { 1996 + "monitor_tag_workspace_id_workspace_id_fk": { 1997 + "name": "monitor_tag_workspace_id_workspace_id_fk", 1998 + "tableFrom": "monitor_tag", 1999 + "tableTo": "workspace", 2000 + "columnsFrom": [ 2001 + "workspace_id" 2002 + ], 2003 + "columnsTo": [ 2004 + "id" 2005 + ], 2006 + "onDelete": "cascade", 2007 + "onUpdate": "no action" 2008 + } 2009 + }, 2010 + "compositePrimaryKeys": {}, 2011 + "uniqueConstraints": {}, 2012 + "checkConstraints": {} 2013 + }, 2014 + "monitor_tag_to_monitor": { 2015 + "name": "monitor_tag_to_monitor", 2016 + "columns": { 2017 + "monitor_id": { 2018 + "name": "monitor_id", 2019 + "type": "integer", 2020 + "primaryKey": false, 2021 + "notNull": true, 2022 + "autoincrement": false 2023 + }, 2024 + "monitor_tag_id": { 2025 + "name": "monitor_tag_id", 2026 + "type": "integer", 2027 + "primaryKey": false, 2028 + "notNull": true, 2029 + "autoincrement": false 2030 + }, 2031 + "created_at": { 2032 + "name": "created_at", 2033 + "type": "integer", 2034 + "primaryKey": false, 2035 + "notNull": false, 2036 + "autoincrement": false, 2037 + "default": "(strftime('%s', 'now'))" 2038 + } 2039 + }, 2040 + "indexes": {}, 2041 + "foreignKeys": { 2042 + "monitor_tag_to_monitor_monitor_id_monitor_id_fk": { 2043 + "name": "monitor_tag_to_monitor_monitor_id_monitor_id_fk", 2044 + "tableFrom": "monitor_tag_to_monitor", 2045 + "tableTo": "monitor", 2046 + "columnsFrom": [ 2047 + "monitor_id" 2048 + ], 2049 + "columnsTo": [ 2050 + "id" 2051 + ], 2052 + "onDelete": "cascade", 2053 + "onUpdate": "no action" 2054 + }, 2055 + "monitor_tag_to_monitor_monitor_tag_id_monitor_tag_id_fk": { 2056 + "name": "monitor_tag_to_monitor_monitor_tag_id_monitor_tag_id_fk", 2057 + "tableFrom": "monitor_tag_to_monitor", 2058 + "tableTo": "monitor_tag", 2059 + "columnsFrom": [ 2060 + "monitor_tag_id" 2061 + ], 2062 + "columnsTo": [ 2063 + "id" 2064 + ], 2065 + "onDelete": "cascade", 2066 + "onUpdate": "no action" 2067 + } 2068 + }, 2069 + "compositePrimaryKeys": { 2070 + "monitor_tag_to_monitor_monitor_id_monitor_tag_id_pk": { 2071 + "columns": [ 2072 + "monitor_id", 2073 + "monitor_tag_id" 2074 + ], 2075 + "name": "monitor_tag_to_monitor_monitor_id_monitor_tag_id_pk" 2076 + } 2077 + }, 2078 + "uniqueConstraints": {}, 2079 + "checkConstraints": {} 2080 + }, 2081 + "application": { 2082 + "name": "application", 2083 + "columns": { 2084 + "id": { 2085 + "name": "id", 2086 + "type": "integer", 2087 + "primaryKey": true, 2088 + "notNull": true, 2089 + "autoincrement": false 2090 + }, 2091 + "name": { 2092 + "name": "name", 2093 + "type": "text", 2094 + "primaryKey": false, 2095 + "notNull": false, 2096 + "autoincrement": false 2097 + }, 2098 + "dsn": { 2099 + "name": "dsn", 2100 + "type": "text", 2101 + "primaryKey": false, 2102 + "notNull": false, 2103 + "autoincrement": false 2104 + }, 2105 + "workspace_id": { 2106 + "name": "workspace_id", 2107 + "type": "integer", 2108 + "primaryKey": false, 2109 + "notNull": false, 2110 + "autoincrement": false 2111 + }, 2112 + "created_at": { 2113 + "name": "created_at", 2114 + "type": "integer", 2115 + "primaryKey": false, 2116 + "notNull": false, 2117 + "autoincrement": false, 2118 + "default": "(strftime('%s', 'now'))" 2119 + }, 2120 + "updated_at": { 2121 + "name": "updated_at", 2122 + "type": "integer", 2123 + "primaryKey": false, 2124 + "notNull": false, 2125 + "autoincrement": false, 2126 + "default": "(strftime('%s', 'now'))" 2127 + } 2128 + }, 2129 + "indexes": { 2130 + "application_dsn_unique": { 2131 + "name": "application_dsn_unique", 2132 + "columns": [ 2133 + "dsn" 2134 + ], 2135 + "isUnique": true 2136 + } 2137 + }, 2138 + "foreignKeys": { 2139 + "application_workspace_id_workspace_id_fk": { 2140 + "name": "application_workspace_id_workspace_id_fk", 2141 + "tableFrom": "application", 2142 + "tableTo": "workspace", 2143 + "columnsFrom": [ 2144 + "workspace_id" 2145 + ], 2146 + "columnsTo": [ 2147 + "id" 2148 + ], 2149 + "onDelete": "no action", 2150 + "onUpdate": "no action" 2151 + } 2152 + }, 2153 + "compositePrimaryKeys": {}, 2154 + "uniqueConstraints": {}, 2155 + "checkConstraints": {} 2156 + }, 2157 + "maintenance": { 2158 + "name": "maintenance", 2159 + "columns": { 2160 + "id": { 2161 + "name": "id", 2162 + "type": "integer", 2163 + "primaryKey": true, 2164 + "notNull": true, 2165 + "autoincrement": false 2166 + }, 2167 + "title": { 2168 + "name": "title", 2169 + "type": "text(256)", 2170 + "primaryKey": false, 2171 + "notNull": true, 2172 + "autoincrement": false 2173 + }, 2174 + "message": { 2175 + "name": "message", 2176 + "type": "text", 2177 + "primaryKey": false, 2178 + "notNull": true, 2179 + "autoincrement": false 2180 + }, 2181 + "from": { 2182 + "name": "from", 2183 + "type": "integer", 2184 + "primaryKey": false, 2185 + "notNull": true, 2186 + "autoincrement": false 2187 + }, 2188 + "to": { 2189 + "name": "to", 2190 + "type": "integer", 2191 + "primaryKey": false, 2192 + "notNull": true, 2193 + "autoincrement": false 2194 + }, 2195 + "workspace_id": { 2196 + "name": "workspace_id", 2197 + "type": "integer", 2198 + "primaryKey": false, 2199 + "notNull": false, 2200 + "autoincrement": false 2201 + }, 2202 + "page_id": { 2203 + "name": "page_id", 2204 + "type": "integer", 2205 + "primaryKey": false, 2206 + "notNull": false, 2207 + "autoincrement": false 2208 + }, 2209 + "created_at": { 2210 + "name": "created_at", 2211 + "type": "integer", 2212 + "primaryKey": false, 2213 + "notNull": false, 2214 + "autoincrement": false, 2215 + "default": "(strftime('%s', 'now'))" 2216 + }, 2217 + "updated_at": { 2218 + "name": "updated_at", 2219 + "type": "integer", 2220 + "primaryKey": false, 2221 + "notNull": false, 2222 + "autoincrement": false, 2223 + "default": "(strftime('%s', 'now'))" 2224 + } 2225 + }, 2226 + "indexes": {}, 2227 + "foreignKeys": { 2228 + "maintenance_workspace_id_workspace_id_fk": { 2229 + "name": "maintenance_workspace_id_workspace_id_fk", 2230 + "tableFrom": "maintenance", 2231 + "tableTo": "workspace", 2232 + "columnsFrom": [ 2233 + "workspace_id" 2234 + ], 2235 + "columnsTo": [ 2236 + "id" 2237 + ], 2238 + "onDelete": "no action", 2239 + "onUpdate": "no action" 2240 + }, 2241 + "maintenance_page_id_page_id_fk": { 2242 + "name": "maintenance_page_id_page_id_fk", 2243 + "tableFrom": "maintenance", 2244 + "tableTo": "page", 2245 + "columnsFrom": [ 2246 + "page_id" 2247 + ], 2248 + "columnsTo": [ 2249 + "id" 2250 + ], 2251 + "onDelete": "cascade", 2252 + "onUpdate": "no action" 2253 + } 2254 + }, 2255 + "compositePrimaryKeys": {}, 2256 + "uniqueConstraints": {}, 2257 + "checkConstraints": {} 2258 + }, 2259 + "maintenance_to_monitor": { 2260 + "name": "maintenance_to_monitor", 2261 + "columns": { 2262 + "maintenance_id": { 2263 + "name": "maintenance_id", 2264 + "type": "integer", 2265 + "primaryKey": false, 2266 + "notNull": true, 2267 + "autoincrement": false 2268 + }, 2269 + "monitor_id": { 2270 + "name": "monitor_id", 2271 + "type": "integer", 2272 + "primaryKey": false, 2273 + "notNull": true, 2274 + "autoincrement": false 2275 + }, 2276 + "created_at": { 2277 + "name": "created_at", 2278 + "type": "integer", 2279 + "primaryKey": false, 2280 + "notNull": false, 2281 + "autoincrement": false, 2282 + "default": "(strftime('%s', 'now'))" 2283 + } 2284 + }, 2285 + "indexes": {}, 2286 + "foreignKeys": { 2287 + "maintenance_to_monitor_maintenance_id_maintenance_id_fk": { 2288 + "name": "maintenance_to_monitor_maintenance_id_maintenance_id_fk", 2289 + "tableFrom": "maintenance_to_monitor", 2290 + "tableTo": "maintenance", 2291 + "columnsFrom": [ 2292 + "maintenance_id" 2293 + ], 2294 + "columnsTo": [ 2295 + "id" 2296 + ], 2297 + "onDelete": "cascade", 2298 + "onUpdate": "no action" 2299 + }, 2300 + "maintenance_to_monitor_monitor_id_monitor_id_fk": { 2301 + "name": "maintenance_to_monitor_monitor_id_monitor_id_fk", 2302 + "tableFrom": "maintenance_to_monitor", 2303 + "tableTo": "monitor", 2304 + "columnsFrom": [ 2305 + "monitor_id" 2306 + ], 2307 + "columnsTo": [ 2308 + "id" 2309 + ], 2310 + "onDelete": "cascade", 2311 + "onUpdate": "no action" 2312 + } 2313 + }, 2314 + "compositePrimaryKeys": { 2315 + "maintenance_to_monitor_maintenance_id_monitor_id_pk": { 2316 + "columns": [ 2317 + "maintenance_id", 2318 + "monitor_id" 2319 + ], 2320 + "name": "maintenance_to_monitor_maintenance_id_monitor_id_pk" 2321 + } 2322 + }, 2323 + "uniqueConstraints": {}, 2324 + "checkConstraints": {} 2325 + }, 2326 + "check": { 2327 + "name": "check", 2328 + "columns": { 2329 + "id": { 2330 + "name": "id", 2331 + "type": "integer", 2332 + "primaryKey": true, 2333 + "notNull": true, 2334 + "autoincrement": true 2335 + }, 2336 + "regions": { 2337 + "name": "regions", 2338 + "type": "text", 2339 + "primaryKey": false, 2340 + "notNull": true, 2341 + "autoincrement": false, 2342 + "default": "''" 2343 + }, 2344 + "url": { 2345 + "name": "url", 2346 + "type": "text(4096)", 2347 + "primaryKey": false, 2348 + "notNull": true, 2349 + "autoincrement": false 2350 + }, 2351 + "headers": { 2352 + "name": "headers", 2353 + "type": "text", 2354 + "primaryKey": false, 2355 + "notNull": false, 2356 + "autoincrement": false, 2357 + "default": "''" 2358 + }, 2359 + "body": { 2360 + "name": "body", 2361 + "type": "text", 2362 + "primaryKey": false, 2363 + "notNull": false, 2364 + "autoincrement": false, 2365 + "default": "''" 2366 + }, 2367 + "method": { 2368 + "name": "method", 2369 + "type": "text", 2370 + "primaryKey": false, 2371 + "notNull": false, 2372 + "autoincrement": false, 2373 + "default": "'GET'" 2374 + }, 2375 + "count_requests": { 2376 + "name": "count_requests", 2377 + "type": "integer", 2378 + "primaryKey": false, 2379 + "notNull": false, 2380 + "autoincrement": false, 2381 + "default": 1 2382 + }, 2383 + "workspace_id": { 2384 + "name": "workspace_id", 2385 + "type": "integer", 2386 + "primaryKey": false, 2387 + "notNull": false, 2388 + "autoincrement": false 2389 + }, 2390 + "created_at": { 2391 + "name": "created_at", 2392 + "type": "integer", 2393 + "primaryKey": false, 2394 + "notNull": false, 2395 + "autoincrement": false, 2396 + "default": "(strftime('%s', 'now'))" 2397 + } 2398 + }, 2399 + "indexes": {}, 2400 + "foreignKeys": { 2401 + "check_workspace_id_workspace_id_fk": { 2402 + "name": "check_workspace_id_workspace_id_fk", 2403 + "tableFrom": "check", 2404 + "tableTo": "workspace", 2405 + "columnsFrom": [ 2406 + "workspace_id" 2407 + ], 2408 + "columnsTo": [ 2409 + "id" 2410 + ], 2411 + "onDelete": "no action", 2412 + "onUpdate": "no action" 2413 + } 2414 + }, 2415 + "compositePrimaryKeys": {}, 2416 + "uniqueConstraints": {}, 2417 + "checkConstraints": {} 2418 + }, 2419 + "monitor_run": { 2420 + "name": "monitor_run", 2421 + "columns": { 2422 + "id": { 2423 + "name": "id", 2424 + "type": "integer", 2425 + "primaryKey": true, 2426 + "notNull": true, 2427 + "autoincrement": false 2428 + }, 2429 + "workspace_id": { 2430 + "name": "workspace_id", 2431 + "type": "integer", 2432 + "primaryKey": false, 2433 + "notNull": false, 2434 + "autoincrement": false 2435 + }, 2436 + "monitor_id": { 2437 + "name": "monitor_id", 2438 + "type": "integer", 2439 + "primaryKey": false, 2440 + "notNull": false, 2441 + "autoincrement": false 2442 + }, 2443 + "runned_at": { 2444 + "name": "runned_at", 2445 + "type": "integer", 2446 + "primaryKey": false, 2447 + "notNull": false, 2448 + "autoincrement": false 2449 + }, 2450 + "created_at": { 2451 + "name": "created_at", 2452 + "type": "integer", 2453 + "primaryKey": false, 2454 + "notNull": false, 2455 + "autoincrement": false, 2456 + "default": "(strftime('%s', 'now'))" 2457 + } 2458 + }, 2459 + "indexes": {}, 2460 + "foreignKeys": { 2461 + "monitor_run_workspace_id_workspace_id_fk": { 2462 + "name": "monitor_run_workspace_id_workspace_id_fk", 2463 + "tableFrom": "monitor_run", 2464 + "tableTo": "workspace", 2465 + "columnsFrom": [ 2466 + "workspace_id" 2467 + ], 2468 + "columnsTo": [ 2469 + "id" 2470 + ], 2471 + "onDelete": "no action", 2472 + "onUpdate": "no action" 2473 + }, 2474 + "monitor_run_monitor_id_monitor_id_fk": { 2475 + "name": "monitor_run_monitor_id_monitor_id_fk", 2476 + "tableFrom": "monitor_run", 2477 + "tableTo": "monitor", 2478 + "columnsFrom": [ 2479 + "monitor_id" 2480 + ], 2481 + "columnsTo": [ 2482 + "id" 2483 + ], 2484 + "onDelete": "no action", 2485 + "onUpdate": "no action" 2486 + } 2487 + }, 2488 + "compositePrimaryKeys": {}, 2489 + "uniqueConstraints": {}, 2490 + "checkConstraints": {} 2491 + }, 2492 + "private_location": { 2493 + "name": "private_location", 2494 + "columns": { 2495 + "id": { 2496 + "name": "id", 2497 + "type": "integer", 2498 + "primaryKey": true, 2499 + "notNull": true, 2500 + "autoincrement": false 2501 + }, 2502 + "name": { 2503 + "name": "name", 2504 + "type": "text", 2505 + "primaryKey": false, 2506 + "notNull": true, 2507 + "autoincrement": false 2508 + }, 2509 + "token": { 2510 + "name": "token", 2511 + "type": "text", 2512 + "primaryKey": false, 2513 + "notNull": true, 2514 + "autoincrement": false 2515 + }, 2516 + "last_seen_at": { 2517 + "name": "last_seen_at", 2518 + "type": "integer", 2519 + "primaryKey": false, 2520 + "notNull": false, 2521 + "autoincrement": false 2522 + }, 2523 + "workspace_id": { 2524 + "name": "workspace_id", 2525 + "type": "integer", 2526 + "primaryKey": false, 2527 + "notNull": false, 2528 + "autoincrement": false 2529 + }, 2530 + "created_at": { 2531 + "name": "created_at", 2532 + "type": "integer", 2533 + "primaryKey": false, 2534 + "notNull": false, 2535 + "autoincrement": false, 2536 + "default": "(strftime('%s', 'now'))" 2537 + }, 2538 + "updated_at": { 2539 + "name": "updated_at", 2540 + "type": "integer", 2541 + "primaryKey": false, 2542 + "notNull": false, 2543 + "autoincrement": false, 2544 + "default": "(strftime('%s', 'now'))" 2545 + } 2546 + }, 2547 + "indexes": {}, 2548 + "foreignKeys": { 2549 + "private_location_workspace_id_workspace_id_fk": { 2550 + "name": "private_location_workspace_id_workspace_id_fk", 2551 + "tableFrom": "private_location", 2552 + "tableTo": "workspace", 2553 + "columnsFrom": [ 2554 + "workspace_id" 2555 + ], 2556 + "columnsTo": [ 2557 + "id" 2558 + ], 2559 + "onDelete": "no action", 2560 + "onUpdate": "no action" 2561 + } 2562 + }, 2563 + "compositePrimaryKeys": {}, 2564 + "uniqueConstraints": {}, 2565 + "checkConstraints": {} 2566 + }, 2567 + "private_location_to_monitor": { 2568 + "name": "private_location_to_monitor", 2569 + "columns": { 2570 + "private_location_id": { 2571 + "name": "private_location_id", 2572 + "type": "integer", 2573 + "primaryKey": false, 2574 + "notNull": false, 2575 + "autoincrement": false 2576 + }, 2577 + "monitor_id": { 2578 + "name": "monitor_id", 2579 + "type": "integer", 2580 + "primaryKey": false, 2581 + "notNull": false, 2582 + "autoincrement": false 2583 + }, 2584 + "created_at": { 2585 + "name": "created_at", 2586 + "type": "integer", 2587 + "primaryKey": false, 2588 + "notNull": false, 2589 + "autoincrement": false, 2590 + "default": "(strftime('%s', 'now'))" 2591 + }, 2592 + "deleted_at": { 2593 + "name": "deleted_at", 2594 + "type": "integer", 2595 + "primaryKey": false, 2596 + "notNull": false, 2597 + "autoincrement": false 2598 + } 2599 + }, 2600 + "indexes": {}, 2601 + "foreignKeys": { 2602 + "private_location_to_monitor_private_location_id_private_location_id_fk": { 2603 + "name": "private_location_to_monitor_private_location_id_private_location_id_fk", 2604 + "tableFrom": "private_location_to_monitor", 2605 + "tableTo": "private_location", 2606 + "columnsFrom": [ 2607 + "private_location_id" 2608 + ], 2609 + "columnsTo": [ 2610 + "id" 2611 + ], 2612 + "onDelete": "cascade", 2613 + "onUpdate": "no action" 2614 + }, 2615 + "private_location_to_monitor_monitor_id_monitor_id_fk": { 2616 + "name": "private_location_to_monitor_monitor_id_monitor_id_fk", 2617 + "tableFrom": "private_location_to_monitor", 2618 + "tableTo": "monitor", 2619 + "columnsFrom": [ 2620 + "monitor_id" 2621 + ], 2622 + "columnsTo": [ 2623 + "id" 2624 + ], 2625 + "onDelete": "cascade", 2626 + "onUpdate": "no action" 2627 + } 2628 + }, 2629 + "compositePrimaryKeys": {}, 2630 + "uniqueConstraints": {}, 2631 + "checkConstraints": {} 2632 + }, 2633 + "monitor_group": { 2634 + "name": "monitor_group", 2635 + "columns": { 2636 + "id": { 2637 + "name": "id", 2638 + "type": "integer", 2639 + "primaryKey": true, 2640 + "notNull": true, 2641 + "autoincrement": false 2642 + }, 2643 + "workspace_id": { 2644 + "name": "workspace_id", 2645 + "type": "integer", 2646 + "primaryKey": false, 2647 + "notNull": true, 2648 + "autoincrement": false 2649 + }, 2650 + "page_id": { 2651 + "name": "page_id", 2652 + "type": "integer", 2653 + "primaryKey": false, 2654 + "notNull": true, 2655 + "autoincrement": false 2656 + }, 2657 + "name": { 2658 + "name": "name", 2659 + "type": "text", 2660 + "primaryKey": false, 2661 + "notNull": true, 2662 + "autoincrement": false 2663 + }, 2664 + "created_at": { 2665 + "name": "created_at", 2666 + "type": "integer", 2667 + "primaryKey": false, 2668 + "notNull": false, 2669 + "autoincrement": false, 2670 + "default": "(strftime('%s', 'now'))" 2671 + }, 2672 + "updated_at": { 2673 + "name": "updated_at", 2674 + "type": "integer", 2675 + "primaryKey": false, 2676 + "notNull": false, 2677 + "autoincrement": false, 2678 + "default": "(strftime('%s', 'now'))" 2679 + } 2680 + }, 2681 + "indexes": {}, 2682 + "foreignKeys": { 2683 + "monitor_group_workspace_id_workspace_id_fk": { 2684 + "name": "monitor_group_workspace_id_workspace_id_fk", 2685 + "tableFrom": "monitor_group", 2686 + "tableTo": "workspace", 2687 + "columnsFrom": [ 2688 + "workspace_id" 2689 + ], 2690 + "columnsTo": [ 2691 + "id" 2692 + ], 2693 + "onDelete": "cascade", 2694 + "onUpdate": "no action" 2695 + }, 2696 + "monitor_group_page_id_page_id_fk": { 2697 + "name": "monitor_group_page_id_page_id_fk", 2698 + "tableFrom": "monitor_group", 2699 + "tableTo": "page", 2700 + "columnsFrom": [ 2701 + "page_id" 2702 + ], 2703 + "columnsTo": [ 2704 + "id" 2705 + ], 2706 + "onDelete": "cascade", 2707 + "onUpdate": "no action" 2708 + } 2709 + }, 2710 + "compositePrimaryKeys": {}, 2711 + "uniqueConstraints": {}, 2712 + "checkConstraints": {} 2713 + }, 2714 + "viewer": { 2715 + "name": "viewer", 2716 + "columns": { 2717 + "id": { 2718 + "name": "id", 2719 + "type": "integer", 2720 + "primaryKey": true, 2721 + "notNull": true, 2722 + "autoincrement": false 2723 + }, 2724 + "name": { 2725 + "name": "name", 2726 + "type": "text", 2727 + "primaryKey": false, 2728 + "notNull": false, 2729 + "autoincrement": false 2730 + }, 2731 + "email": { 2732 + "name": "email", 2733 + "type": "text", 2734 + "primaryKey": false, 2735 + "notNull": false, 2736 + "autoincrement": false 2737 + }, 2738 + "emailVerified": { 2739 + "name": "emailVerified", 2740 + "type": "integer", 2741 + "primaryKey": false, 2742 + "notNull": false, 2743 + "autoincrement": false 2744 + }, 2745 + "image": { 2746 + "name": "image", 2747 + "type": "text", 2748 + "primaryKey": false, 2749 + "notNull": false, 2750 + "autoincrement": false 2751 + }, 2752 + "created_at": { 2753 + "name": "created_at", 2754 + "type": "integer", 2755 + "primaryKey": false, 2756 + "notNull": false, 2757 + "autoincrement": false, 2758 + "default": "(strftime('%s', 'now'))" 2759 + }, 2760 + "updated_at": { 2761 + "name": "updated_at", 2762 + "type": "integer", 2763 + "primaryKey": false, 2764 + "notNull": false, 2765 + "autoincrement": false, 2766 + "default": "(strftime('%s', 'now'))" 2767 + } 2768 + }, 2769 + "indexes": { 2770 + "viewer_email_unique": { 2771 + "name": "viewer_email_unique", 2772 + "columns": [ 2773 + "email" 2774 + ], 2775 + "isUnique": true 2776 + } 2777 + }, 2778 + "foreignKeys": {}, 2779 + "compositePrimaryKeys": {}, 2780 + "uniqueConstraints": {}, 2781 + "checkConstraints": {} 2782 + }, 2783 + "viewer_accounts": { 2784 + "name": "viewer_accounts", 2785 + "columns": { 2786 + "user_id": { 2787 + "name": "user_id", 2788 + "type": "text", 2789 + "primaryKey": false, 2790 + "notNull": true, 2791 + "autoincrement": false 2792 + }, 2793 + "type": { 2794 + "name": "type", 2795 + "type": "text", 2796 + "primaryKey": false, 2797 + "notNull": true, 2798 + "autoincrement": false 2799 + }, 2800 + "provider": { 2801 + "name": "provider", 2802 + "type": "text", 2803 + "primaryKey": false, 2804 + "notNull": true, 2805 + "autoincrement": false 2806 + }, 2807 + "providerAccountId": { 2808 + "name": "providerAccountId", 2809 + "type": "text", 2810 + "primaryKey": false, 2811 + "notNull": true, 2812 + "autoincrement": false 2813 + }, 2814 + "refresh_token": { 2815 + "name": "refresh_token", 2816 + "type": "text", 2817 + "primaryKey": false, 2818 + "notNull": false, 2819 + "autoincrement": false 2820 + }, 2821 + "access_token": { 2822 + "name": "access_token", 2823 + "type": "text", 2824 + "primaryKey": false, 2825 + "notNull": false, 2826 + "autoincrement": false 2827 + }, 2828 + "expires_at": { 2829 + "name": "expires_at", 2830 + "type": "integer", 2831 + "primaryKey": false, 2832 + "notNull": false, 2833 + "autoincrement": false 2834 + }, 2835 + "token_type": { 2836 + "name": "token_type", 2837 + "type": "text", 2838 + "primaryKey": false, 2839 + "notNull": false, 2840 + "autoincrement": false 2841 + }, 2842 + "scope": { 2843 + "name": "scope", 2844 + "type": "text", 2845 + "primaryKey": false, 2846 + "notNull": false, 2847 + "autoincrement": false 2848 + }, 2849 + "id_token": { 2850 + "name": "id_token", 2851 + "type": "text", 2852 + "primaryKey": false, 2853 + "notNull": false, 2854 + "autoincrement": false 2855 + }, 2856 + "session_state": { 2857 + "name": "session_state", 2858 + "type": "text", 2859 + "primaryKey": false, 2860 + "notNull": false, 2861 + "autoincrement": false 2862 + } 2863 + }, 2864 + "indexes": {}, 2865 + "foreignKeys": { 2866 + "viewer_accounts_user_id_viewer_id_fk": { 2867 + "name": "viewer_accounts_user_id_viewer_id_fk", 2868 + "tableFrom": "viewer_accounts", 2869 + "tableTo": "viewer", 2870 + "columnsFrom": [ 2871 + "user_id" 2872 + ], 2873 + "columnsTo": [ 2874 + "id" 2875 + ], 2876 + "onDelete": "cascade", 2877 + "onUpdate": "no action" 2878 + } 2879 + }, 2880 + "compositePrimaryKeys": { 2881 + "viewer_accounts_provider_providerAccountId_pk": { 2882 + "columns": [ 2883 + "provider", 2884 + "providerAccountId" 2885 + ], 2886 + "name": "viewer_accounts_provider_providerAccountId_pk" 2887 + } 2888 + }, 2889 + "uniqueConstraints": {}, 2890 + "checkConstraints": {} 2891 + }, 2892 + "viewer_session": { 2893 + "name": "viewer_session", 2894 + "columns": { 2895 + "session_token": { 2896 + "name": "session_token", 2897 + "type": "text", 2898 + "primaryKey": true, 2899 + "notNull": true, 2900 + "autoincrement": false 2901 + }, 2902 + "user_id": { 2903 + "name": "user_id", 2904 + "type": "integer", 2905 + "primaryKey": false, 2906 + "notNull": true, 2907 + "autoincrement": false 2908 + }, 2909 + "expires": { 2910 + "name": "expires", 2911 + "type": "integer", 2912 + "primaryKey": false, 2913 + "notNull": true, 2914 + "autoincrement": false 2915 + } 2916 + }, 2917 + "indexes": {}, 2918 + "foreignKeys": { 2919 + "viewer_session_user_id_viewer_id_fk": { 2920 + "name": "viewer_session_user_id_viewer_id_fk", 2921 + "tableFrom": "viewer_session", 2922 + "tableTo": "viewer", 2923 + "columnsFrom": [ 2924 + "user_id" 2925 + ], 2926 + "columnsTo": [ 2927 + "id" 2928 + ], 2929 + "onDelete": "cascade", 2930 + "onUpdate": "no action" 2931 + } 2932 + }, 2933 + "compositePrimaryKeys": {}, 2934 + "uniqueConstraints": {}, 2935 + "checkConstraints": {} 2936 + }, 2937 + "api_key": { 2938 + "name": "api_key", 2939 + "columns": { 2940 + "id": { 2941 + "name": "id", 2942 + "type": "integer", 2943 + "primaryKey": true, 2944 + "notNull": true, 2945 + "autoincrement": true 2946 + }, 2947 + "name": { 2948 + "name": "name", 2949 + "type": "text", 2950 + "primaryKey": false, 2951 + "notNull": true, 2952 + "autoincrement": false 2953 + }, 2954 + "description": { 2955 + "name": "description", 2956 + "type": "text", 2957 + "primaryKey": false, 2958 + "notNull": false, 2959 + "autoincrement": false 2960 + }, 2961 + "prefix": { 2962 + "name": "prefix", 2963 + "type": "text", 2964 + "primaryKey": false, 2965 + "notNull": true, 2966 + "autoincrement": false 2967 + }, 2968 + "hashed_token": { 2969 + "name": "hashed_token", 2970 + "type": "text", 2971 + "primaryKey": false, 2972 + "notNull": true, 2973 + "autoincrement": false 2974 + }, 2975 + "workspace_id": { 2976 + "name": "workspace_id", 2977 + "type": "integer", 2978 + "primaryKey": false, 2979 + "notNull": true, 2980 + "autoincrement": false 2981 + }, 2982 + "created_by_id": { 2983 + "name": "created_by_id", 2984 + "type": "integer", 2985 + "primaryKey": false, 2986 + "notNull": true, 2987 + "autoincrement": false 2988 + }, 2989 + "created_at": { 2990 + "name": "created_at", 2991 + "type": "integer", 2992 + "primaryKey": false, 2993 + "notNull": false, 2994 + "autoincrement": false, 2995 + "default": "(strftime('%s', 'now'))" 2996 + }, 2997 + "expires_at": { 2998 + "name": "expires_at", 2999 + "type": "integer", 3000 + "primaryKey": false, 3001 + "notNull": false, 3002 + "autoincrement": false 3003 + }, 3004 + "last_used_at": { 3005 + "name": "last_used_at", 3006 + "type": "integer", 3007 + "primaryKey": false, 3008 + "notNull": false, 3009 + "autoincrement": false 3010 + } 3011 + }, 3012 + "indexes": { 3013 + "api_key_prefix_unique": { 3014 + "name": "api_key_prefix_unique", 3015 + "columns": [ 3016 + "prefix" 3017 + ], 3018 + "isUnique": true 3019 + }, 3020 + "api_key_hashed_token_unique": { 3021 + "name": "api_key_hashed_token_unique", 3022 + "columns": [ 3023 + "hashed_token" 3024 + ], 3025 + "isUnique": true 3026 + }, 3027 + "api_key_prefix_idx": { 3028 + "name": "api_key_prefix_idx", 3029 + "columns": [ 3030 + "prefix" 3031 + ], 3032 + "isUnique": false 3033 + } 3034 + }, 3035 + "foreignKeys": { 3036 + "api_key_workspace_id_workspace_id_fk": { 3037 + "name": "api_key_workspace_id_workspace_id_fk", 3038 + "tableFrom": "api_key", 3039 + "tableTo": "workspace", 3040 + "columnsFrom": [ 3041 + "workspace_id" 3042 + ], 3043 + "columnsTo": [ 3044 + "id" 3045 + ], 3046 + "onDelete": "cascade", 3047 + "onUpdate": "no action" 3048 + }, 3049 + "api_key_created_by_id_user_id_fk": { 3050 + "name": "api_key_created_by_id_user_id_fk", 3051 + "tableFrom": "api_key", 3052 + "tableTo": "user", 3053 + "columnsFrom": [ 3054 + "created_by_id" 3055 + ], 3056 + "columnsTo": [ 3057 + "id" 3058 + ], 3059 + "onDelete": "cascade", 3060 + "onUpdate": "no action" 3061 + } 3062 + }, 3063 + "compositePrimaryKeys": {}, 3064 + "uniqueConstraints": {}, 3065 + "checkConstraints": {} 3066 + }, 3067 + "maintenance_to_page_component": { 3068 + "name": "maintenance_to_page_component", 3069 + "columns": { 3070 + "maintenance_id": { 3071 + "name": "maintenance_id", 3072 + "type": "integer", 3073 + "primaryKey": false, 3074 + "notNull": true, 3075 + "autoincrement": false 3076 + }, 3077 + "page_component_id": { 3078 + "name": "page_component_id", 3079 + "type": "integer", 3080 + "primaryKey": false, 3081 + "notNull": true, 3082 + "autoincrement": false 3083 + }, 3084 + "created_at": { 3085 + "name": "created_at", 3086 + "type": "integer", 3087 + "primaryKey": false, 3088 + "notNull": false, 3089 + "autoincrement": false, 3090 + "default": "(strftime('%s', 'now'))" 3091 + } 3092 + }, 3093 + "indexes": {}, 3094 + "foreignKeys": { 3095 + "maintenance_to_page_component_maintenance_id_maintenance_id_fk": { 3096 + "name": "maintenance_to_page_component_maintenance_id_maintenance_id_fk", 3097 + "tableFrom": "maintenance_to_page_component", 3098 + "tableTo": "maintenance", 3099 + "columnsFrom": [ 3100 + "maintenance_id" 3101 + ], 3102 + "columnsTo": [ 3103 + "id" 3104 + ], 3105 + "onDelete": "cascade", 3106 + "onUpdate": "no action" 3107 + }, 3108 + "maintenance_to_page_component_page_component_id_page_component_id_fk": { 3109 + "name": "maintenance_to_page_component_page_component_id_page_component_id_fk", 3110 + "tableFrom": "maintenance_to_page_component", 3111 + "tableTo": "page_component", 3112 + "columnsFrom": [ 3113 + "page_component_id" 3114 + ], 3115 + "columnsTo": [ 3116 + "id" 3117 + ], 3118 + "onDelete": "cascade", 3119 + "onUpdate": "no action" 3120 + } 3121 + }, 3122 + "compositePrimaryKeys": { 3123 + "maintenance_to_page_component_maintenance_id_page_component_id_pk": { 3124 + "columns": [ 3125 + "maintenance_id", 3126 + "page_component_id" 3127 + ], 3128 + "name": "maintenance_to_page_component_maintenance_id_page_component_id_pk" 3129 + } 3130 + }, 3131 + "uniqueConstraints": {}, 3132 + "checkConstraints": {} 3133 + }, 3134 + "page_component": { 3135 + "name": "page_component", 3136 + "columns": { 3137 + "id": { 3138 + "name": "id", 3139 + "type": "integer", 3140 + "primaryKey": true, 3141 + "notNull": true, 3142 + "autoincrement": false 3143 + }, 3144 + "workspace_id": { 3145 + "name": "workspace_id", 3146 + "type": "integer", 3147 + "primaryKey": false, 3148 + "notNull": true, 3149 + "autoincrement": false 3150 + }, 3151 + "page_id": { 3152 + "name": "page_id", 3153 + "type": "integer", 3154 + "primaryKey": false, 3155 + "notNull": true, 3156 + "autoincrement": false 3157 + }, 3158 + "type": { 3159 + "name": "type", 3160 + "type": "text", 3161 + "primaryKey": false, 3162 + "notNull": true, 3163 + "autoincrement": false, 3164 + "default": "'monitor'" 3165 + }, 3166 + "monitor_id": { 3167 + "name": "monitor_id", 3168 + "type": "integer", 3169 + "primaryKey": false, 3170 + "notNull": false, 3171 + "autoincrement": false 3172 + }, 3173 + "name": { 3174 + "name": "name", 3175 + "type": "text", 3176 + "primaryKey": false, 3177 + "notNull": true, 3178 + "autoincrement": false 3179 + }, 3180 + "description": { 3181 + "name": "description", 3182 + "type": "text", 3183 + "primaryKey": false, 3184 + "notNull": false, 3185 + "autoincrement": false 3186 + }, 3187 + "order": { 3188 + "name": "order", 3189 + "type": "integer", 3190 + "primaryKey": false, 3191 + "notNull": false, 3192 + "autoincrement": false, 3193 + "default": 0 3194 + }, 3195 + "group_id": { 3196 + "name": "group_id", 3197 + "type": "integer", 3198 + "primaryKey": false, 3199 + "notNull": false, 3200 + "autoincrement": false 3201 + }, 3202 + "group_order": { 3203 + "name": "group_order", 3204 + "type": "integer", 3205 + "primaryKey": false, 3206 + "notNull": false, 3207 + "autoincrement": false, 3208 + "default": 0 3209 + }, 3210 + "created_at": { 3211 + "name": "created_at", 3212 + "type": "integer", 3213 + "primaryKey": false, 3214 + "notNull": false, 3215 + "autoincrement": false, 3216 + "default": "(strftime('%s', 'now'))" 3217 + }, 3218 + "updated_at": { 3219 + "name": "updated_at", 3220 + "type": "integer", 3221 + "primaryKey": false, 3222 + "notNull": false, 3223 + "autoincrement": false, 3224 + "default": "(strftime('%s', 'now'))" 3225 + } 3226 + }, 3227 + "indexes": { 3228 + "page_component_page_id_monitor_id_unique": { 3229 + "name": "page_component_page_id_monitor_id_unique", 3230 + "columns": [ 3231 + "page_id", 3232 + "monitor_id" 3233 + ], 3234 + "isUnique": true 3235 + } 3236 + }, 3237 + "foreignKeys": { 3238 + "page_component_workspace_id_workspace_id_fk": { 3239 + "name": "page_component_workspace_id_workspace_id_fk", 3240 + "tableFrom": "page_component", 3241 + "tableTo": "workspace", 3242 + "columnsFrom": [ 3243 + "workspace_id" 3244 + ], 3245 + "columnsTo": [ 3246 + "id" 3247 + ], 3248 + "onDelete": "cascade", 3249 + "onUpdate": "no action" 3250 + }, 3251 + "page_component_page_id_page_id_fk": { 3252 + "name": "page_component_page_id_page_id_fk", 3253 + "tableFrom": "page_component", 3254 + "tableTo": "page", 3255 + "columnsFrom": [ 3256 + "page_id" 3257 + ], 3258 + "columnsTo": [ 3259 + "id" 3260 + ], 3261 + "onDelete": "cascade", 3262 + "onUpdate": "no action" 3263 + }, 3264 + "page_component_monitor_id_monitor_id_fk": { 3265 + "name": "page_component_monitor_id_monitor_id_fk", 3266 + "tableFrom": "page_component", 3267 + "tableTo": "monitor", 3268 + "columnsFrom": [ 3269 + "monitor_id" 3270 + ], 3271 + "columnsTo": [ 3272 + "id" 3273 + ], 3274 + "onDelete": "cascade", 3275 + "onUpdate": "no action" 3276 + }, 3277 + "page_component_group_id_page_component_groups_id_fk": { 3278 + "name": "page_component_group_id_page_component_groups_id_fk", 3279 + "tableFrom": "page_component", 3280 + "tableTo": "page_component_groups", 3281 + "columnsFrom": [ 3282 + "group_id" 3283 + ], 3284 + "columnsTo": [ 3285 + "id" 3286 + ], 3287 + "onDelete": "set null", 3288 + "onUpdate": "no action" 3289 + } 3290 + }, 3291 + "compositePrimaryKeys": {}, 3292 + "uniqueConstraints": {}, 3293 + "checkConstraints": { 3294 + "page_component_type_check": { 3295 + "name": "page_component_type_check", 3296 + "value": "\"page_component\".\"type\" = 'monitor' AND \"page_component\".\"monitor_id\" IS NOT NULL OR \"page_component\".\"type\" = 'external' AND \"page_component\".\"monitor_id\" IS NULL" 3297 + } 3298 + } 3299 + }, 3300 + "status_report_to_page_component": { 3301 + "name": "status_report_to_page_component", 3302 + "columns": { 3303 + "status_report_id": { 3304 + "name": "status_report_id", 3305 + "type": "integer", 3306 + "primaryKey": false, 3307 + "notNull": true, 3308 + "autoincrement": false 3309 + }, 3310 + "page_component_id": { 3311 + "name": "page_component_id", 3312 + "type": "integer", 3313 + "primaryKey": false, 3314 + "notNull": true, 3315 + "autoincrement": false 3316 + }, 3317 + "created_at": { 3318 + "name": "created_at", 3319 + "type": "integer", 3320 + "primaryKey": false, 3321 + "notNull": false, 3322 + "autoincrement": false, 3323 + "default": "(strftime('%s', 'now'))" 3324 + } 3325 + }, 3326 + "indexes": {}, 3327 + "foreignKeys": { 3328 + "status_report_to_page_component_status_report_id_status_report_id_fk": { 3329 + "name": "status_report_to_page_component_status_report_id_status_report_id_fk", 3330 + "tableFrom": "status_report_to_page_component", 3331 + "tableTo": "status_report", 3332 + "columnsFrom": [ 3333 + "status_report_id" 3334 + ], 3335 + "columnsTo": [ 3336 + "id" 3337 + ], 3338 + "onDelete": "cascade", 3339 + "onUpdate": "no action" 3340 + }, 3341 + "status_report_to_page_component_page_component_id_page_component_id_fk": { 3342 + "name": "status_report_to_page_component_page_component_id_page_component_id_fk", 3343 + "tableFrom": "status_report_to_page_component", 3344 + "tableTo": "page_component", 3345 + "columnsFrom": [ 3346 + "page_component_id" 3347 + ], 3348 + "columnsTo": [ 3349 + "id" 3350 + ], 3351 + "onDelete": "cascade", 3352 + "onUpdate": "no action" 3353 + } 3354 + }, 3355 + "compositePrimaryKeys": { 3356 + "status_report_to_page_component_status_report_id_page_component_id_pk": { 3357 + "columns": [ 3358 + "status_report_id", 3359 + "page_component_id" 3360 + ], 3361 + "name": "status_report_to_page_component_status_report_id_page_component_id_pk" 3362 + } 3363 + }, 3364 + "uniqueConstraints": {}, 3365 + "checkConstraints": {} 3366 + }, 3367 + "page_component_groups": { 3368 + "name": "page_component_groups", 3369 + "columns": { 3370 + "id": { 3371 + "name": "id", 3372 + "type": "integer", 3373 + "primaryKey": true, 3374 + "notNull": true, 3375 + "autoincrement": false 3376 + }, 3377 + "workspace_id": { 3378 + "name": "workspace_id", 3379 + "type": "integer", 3380 + "primaryKey": false, 3381 + "notNull": true, 3382 + "autoincrement": false 3383 + }, 3384 + "page_id": { 3385 + "name": "page_id", 3386 + "type": "integer", 3387 + "primaryKey": false, 3388 + "notNull": true, 3389 + "autoincrement": false 3390 + }, 3391 + "name": { 3392 + "name": "name", 3393 + "type": "text", 3394 + "primaryKey": false, 3395 + "notNull": true, 3396 + "autoincrement": false 3397 + }, 3398 + "created_at": { 3399 + "name": "created_at", 3400 + "type": "integer", 3401 + "primaryKey": false, 3402 + "notNull": false, 3403 + "autoincrement": false, 3404 + "default": "(strftime('%s', 'now'))" 3405 + }, 3406 + "updated_at": { 3407 + "name": "updated_at", 3408 + "type": "integer", 3409 + "primaryKey": false, 3410 + "notNull": false, 3411 + "autoincrement": false, 3412 + "default": "(strftime('%s', 'now'))" 3413 + } 3414 + }, 3415 + "indexes": {}, 3416 + "foreignKeys": { 3417 + "page_component_groups_workspace_id_workspace_id_fk": { 3418 + "name": "page_component_groups_workspace_id_workspace_id_fk", 3419 + "tableFrom": "page_component_groups", 3420 + "tableTo": "workspace", 3421 + "columnsFrom": [ 3422 + "workspace_id" 3423 + ], 3424 + "columnsTo": [ 3425 + "id" 3426 + ], 3427 + "onDelete": "cascade", 3428 + "onUpdate": "no action" 3429 + }, 3430 + "page_component_groups_page_id_page_id_fk": { 3431 + "name": "page_component_groups_page_id_page_id_fk", 3432 + "tableFrom": "page_component_groups", 3433 + "tableTo": "page", 3434 + "columnsFrom": [ 3435 + "page_id" 3436 + ], 3437 + "columnsTo": [ 3438 + "id" 3439 + ], 3440 + "onDelete": "cascade", 3441 + "onUpdate": "no action" 3442 + } 3443 + }, 3444 + "compositePrimaryKeys": {}, 3445 + "uniqueConstraints": {}, 3446 + "checkConstraints": {} 3447 + } 3448 + }, 3449 + "views": {}, 3450 + "enums": {}, 3451 + "_meta": { 3452 + "schemas": {}, 3453 + "tables": {}, 3454 + "columns": {} 3455 + }, 3456 + "internal": { 3457 + "indexes": {} 3458 + } 3459 + }
+7
packages/db/drizzle/meta/_journal.json
··· 379 379 "when": 1768490525276, 380 380 "tag": "0053_dark_orphan", 381 381 "breakpoints": true 382 + }, 383 + { 384 + "idx": 54, 385 + "version": "6", 386 + "when": 1768564852930, 387 + "tag": "0054_bitter_lilandra", 388 + "breakpoints": true 382 389 } 383 390 ] 384 391 }
+1
packages/db/src/index.ts
··· 1 1 export * as schema from "./schema"; 2 2 export * from "drizzle-orm"; 3 3 export * from "./db"; 4 + export * from "./sync"; 4 5 // doing this because the external module not working see : https://github.com/vercel/next.js/issues/43433 5 6 // export * from "./sync-db";
+2
packages/db/src/schema/index.ts
··· 19 19 export * from "./monitor_groups"; 20 20 export * from "./viewers"; 21 21 export * from "./api-keys"; 22 + export * from "./page_components"; 23 + export * from "./page_component_groups";
+2
packages/db/src/schema/page_component_groups/index.ts
··· 1 + export * from "./page_component_groups"; 2 + export * from "./validation";
+39
packages/db/src/schema/page_component_groups/page_component_groups.ts
··· 1 + import { relations, sql } from "drizzle-orm"; 2 + import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; 3 + 4 + import { pageComponent } from "../page_components"; 5 + import { page } from "../pages"; 6 + import { workspace } from "../workspaces"; 7 + 8 + export const pageComponentGroup = sqliteTable("page_component_groups", { 9 + id: integer("id").primaryKey(), 10 + workspaceId: integer("workspace_id") 11 + .references(() => workspace.id, { onDelete: "cascade" }) 12 + .notNull(), 13 + pageId: integer("page_id") 14 + .references(() => page.id, { onDelete: "cascade" }) 15 + .notNull(), 16 + name: text("name").notNull(), 17 + 18 + createdAt: integer("created_at", { mode: "timestamp" }).default( 19 + sql`(strftime('%s', 'now'))`, 20 + ), 21 + updatedAt: integer("updated_at", { mode: "timestamp" }).default( 22 + sql`(strftime('%s', 'now'))`, 23 + ), 24 + }); 25 + 26 + export const pageComponentGroupRelations = relations( 27 + pageComponentGroup, 28 + ({ one, many }) => ({ 29 + workspace: one(workspace, { 30 + fields: [pageComponentGroup.workspaceId], 31 + references: [workspace.id], 32 + }), 33 + page: one(page, { 34 + fields: [pageComponentGroup.pageId], 35 + references: [page.id], 36 + }), 37 + pageComponents: many(pageComponent), 38 + }), 39 + );
+15
packages/db/src/schema/page_component_groups/validation.ts
··· 1 + import { createInsertSchema, createSelectSchema } from "drizzle-zod"; 2 + import type { z } from "zod"; 3 + 4 + import { pageComponentGroup } from "./page_component_groups"; 5 + 6 + export const selectPageComponentGroupSchema = 7 + createSelectSchema(pageComponentGroup); 8 + 9 + export const insertPageComponentGroupSchema = 10 + createInsertSchema(pageComponentGroup); 11 + 12 + export type InsertPageComponentGroup = z.infer< 13 + typeof insertPageComponentGroupSchema 14 + >; 15 + export type PageComponentGroup = z.infer<typeof selectPageComponentGroupSchema>;
+2
packages/db/src/schema/page_components/index.ts
··· 1 + export * from "./page_components"; 2 + export * from "./validation";
+150
packages/db/src/schema/page_components/page_components.ts
··· 1 + import { relations, sql } from "drizzle-orm"; 2 + import { 3 + check, 4 + integer, 5 + primaryKey, 6 + sqliteTable, 7 + text, 8 + unique, 9 + } from "drizzle-orm/sqlite-core"; 10 + 11 + import { maintenance } from "../maintenances"; 12 + import { monitor } from "../monitors"; 13 + import { pageComponentGroup } from "../page_component_groups"; 14 + import { page } from "../pages"; 15 + import { statusReport } from "../status_reports"; 16 + import { workspace } from "../workspaces"; 17 + 18 + export const pageComponentTypes = ["external", "monitor"] as const; 19 + 20 + export const pageComponent = sqliteTable( 21 + "page_component", 22 + { 23 + id: integer("id").primaryKey(), 24 + workspaceId: integer("workspace_id") 25 + .notNull() 26 + .references(() => workspace.id, { onDelete: "cascade" }), 27 + pageId: integer("page_id") 28 + .notNull() 29 + .references(() => page.id, { onDelete: "cascade" }), 30 + type: text("type", { enum: pageComponentTypes }) 31 + .notNull() 32 + .default("monitor"), 33 + monitorId: integer("monitor_id").references(() => monitor.id, { 34 + onDelete: "cascade", 35 + }), 36 + name: text("name").notNull(), 37 + description: text("description"), 38 + order: integer("order").default(0), 39 + groupId: integer("group_id").references(() => pageComponentGroup.id, { 40 + onDelete: "set null", 41 + }), 42 + groupOrder: integer("group_order").default(0), 43 + 44 + createdAt: integer("created_at", { mode: "timestamp" }).default( 45 + sql`(strftime('%s', 'now'))`, 46 + ), 47 + updatedAt: integer("updated_at", { mode: "timestamp" }).default( 48 + sql`(strftime('%s', 'now'))`, 49 + ), 50 + }, 51 + (t) => [ 52 + unique("page_component_page_id_monitor_id_unique").on( 53 + t.pageId, 54 + t.monitorId, 55 + ), 56 + check( 57 + "page_component_type_check", 58 + // NOTE: This check ensures that either the component is a monitor or an external component, but not both. 59 + sql`${t.type} = 'monitor' AND ${t.monitorId} IS NOT NULL OR ${t.type} = 'external' AND ${t.monitorId} IS NULL`, 60 + ), 61 + ], 62 + ); 63 + 64 + export const pageComponentRelations = relations( 65 + pageComponent, 66 + ({ one, many }) => ({ 67 + workspace: one(workspace, { 68 + fields: [pageComponent.workspaceId], 69 + references: [workspace.id], 70 + }), 71 + page: one(page, { 72 + fields: [pageComponent.pageId], 73 + references: [page.id], 74 + }), 75 + monitor: one(monitor, { 76 + fields: [pageComponent.monitorId], 77 + references: [monitor.id], 78 + }), 79 + group: one(pageComponentGroup, { 80 + fields: [pageComponent.groupId], 81 + references: [pageComponentGroup.id], 82 + }), 83 + statusReportsToPageComponents: many(statusReportsToPageComponents), 84 + maintenancesToPageComponents: many(maintenancesToPageComponents), 85 + }), 86 + ); 87 + 88 + export const maintenancesToPageComponents = sqliteTable( 89 + "maintenance_to_page_component", 90 + { 91 + maintenanceId: integer("maintenance_id") 92 + .notNull() 93 + .references(() => maintenance.id, { onDelete: "cascade" }), 94 + pageComponentId: integer("page_component_id") 95 + .notNull() 96 + .references(() => pageComponent.id, { onDelete: "cascade" }), 97 + createdAt: integer("created_at", { mode: "timestamp" }).default( 98 + sql`(strftime('%s', 'now'))`, 99 + ), 100 + }, 101 + (t) => ({ 102 + pk: primaryKey({ columns: [t.maintenanceId, t.pageComponentId] }), 103 + }), 104 + ); 105 + 106 + export const maintenancesToPageComponentsRelations = relations( 107 + maintenancesToPageComponents, 108 + ({ one }) => ({ 109 + maintenance: one(maintenance, { 110 + fields: [maintenancesToPageComponents.maintenanceId], 111 + references: [maintenance.id], 112 + }), 113 + pageComponent: one(pageComponent, { 114 + fields: [maintenancesToPageComponents.pageComponentId], 115 + references: [pageComponent.id], 116 + }), 117 + }), 118 + ); 119 + 120 + export const statusReportsToPageComponents = sqliteTable( 121 + "status_report_to_page_component", 122 + { 123 + statusReportId: integer("status_report_id") 124 + .notNull() 125 + .references(() => statusReport.id, { onDelete: "cascade" }), 126 + pageComponentId: integer("page_component_id") 127 + .notNull() 128 + .references(() => pageComponent.id, { onDelete: "cascade" }), 129 + createdAt: integer("created_at", { mode: "timestamp" }).default( 130 + sql`(strftime('%s', 'now'))`, 131 + ), 132 + }, 133 + (t) => ({ 134 + pk: primaryKey({ columns: [t.statusReportId, t.pageComponentId] }), 135 + }), 136 + ); 137 + 138 + export const statusReportsToPageComponentsRelations = relations( 139 + statusReportsToPageComponents, 140 + ({ one }) => ({ 141 + statusReport: one(statusReport, { 142 + fields: [statusReportsToPageComponents.statusReportId], 143 + references: [statusReport.id], 144 + }), 145 + pageComponent: one(pageComponent, { 146 + fields: [statusReportsToPageComponents.pageComponentId], 147 + references: [pageComponent.id], 148 + }), 149 + }), 150 + );
+29
packages/db/src/schema/page_components/validation.ts
··· 1 + import { createInsertSchema, createSelectSchema } from "drizzle-zod"; 2 + import type { z } from "zod"; 3 + 4 + import { pageComponent } from "./page_components"; 5 + 6 + export const selectPageComponentSchema = createSelectSchema(pageComponent); 7 + 8 + export const insertPageComponentSchema = createInsertSchema(pageComponent, { 9 + name: (schema) => schema.min(1), 10 + }).refine( 11 + (data) => { 12 + // monitorId must be set when type='monitor' 13 + if (data.type === "monitor" && !data.monitorId) { 14 + return false; 15 + } 16 + // monitorId must be null when type='external' 17 + if (data.type === "external" && data.monitorId) { 18 + return false; 19 + } 20 + return true; 21 + }, 22 + { 23 + message: 24 + "monitorId must be set when type is 'monitor' and must be null when type is 'external'", 25 + }, 26 + ); 27 + 28 + export type InsertPageComponent = z.infer<typeof insertPageComponentSchema>; 29 + export type PageComponent = z.infer<typeof selectPageComponentSchema>;
+515
packages/db/src/sync.ts
··· 1 + import { and, eq, inArray } from "drizzle-orm"; 2 + 3 + import type { db } from "./db"; 4 + import { 5 + maintenance, 6 + maintenancesToPageComponents, 7 + monitor, 8 + pageComponent, 9 + pageComponentGroup, 10 + statusReport, 11 + statusReportsToPageComponents, 12 + } from "./schema"; 13 + 14 + // Type that works with both LibSQLDatabase and SQLiteTransaction 15 + // Using any to allow both db instance and transaction to be passed 16 + // biome-ignore lint/suspicious/noExplicitAny: Compatible with both db and transaction 17 + type DB = typeof db; 18 + // Extract transaction type from the callback parameter of db.transaction() 19 + type Transaction = Parameters<Parameters<DB["transaction"]>[0]>[0]; 20 + 21 + // ============================================================================ 22 + // Monitor Group <-> Page Component Group Sync 23 + // ============================================================================ 24 + 25 + /** 26 + * Syncs a monitor group insert to page_component_groups 27 + */ 28 + export async function syncMonitorGroupInsert( 29 + db: DB | Transaction, 30 + data: { 31 + id: number; 32 + workspaceId: number; 33 + pageId: number; 34 + name: string; 35 + }, 36 + ) { 37 + await db 38 + .insert(pageComponentGroup) 39 + .values({ 40 + id: data.id, 41 + workspaceId: data.workspaceId, 42 + pageId: data.pageId, 43 + name: data.name, 44 + }) 45 + .onConflictDoNothing(); 46 + } 47 + 48 + /** 49 + * Syncs a monitor group delete to page_component_groups 50 + */ 51 + export async function syncMonitorGroupDelete( 52 + db: DB | Transaction, 53 + monitorGroupId: number, 54 + ) { 55 + await db 56 + .delete(pageComponentGroup) 57 + .where(eq(pageComponentGroup.id, monitorGroupId)); 58 + } 59 + 60 + /** 61 + * Syncs multiple monitor group deletes to page_component_groups 62 + */ 63 + export async function syncMonitorGroupDeleteMany( 64 + db: DB | Transaction, 65 + monitorGroupIds: number[], 66 + ) { 67 + if (monitorGroupIds.length === 0) return; 68 + await db 69 + .delete(pageComponentGroup) 70 + .where(inArray(pageComponentGroup.id, monitorGroupIds)); 71 + } 72 + 73 + // ============================================================================ 74 + // Monitors to Pages <-> Page Component Sync 75 + // ============================================================================ 76 + 77 + /** 78 + * Syncs a monitors_to_pages insert to page_component 79 + * Requires monitor data to get name and workspace_id 80 + */ 81 + export async function syncMonitorsToPageInsert( 82 + db: DB | Transaction, 83 + data: { 84 + monitorId: number; 85 + pageId: number; 86 + order?: number; 87 + monitorGroupId?: number | null; 88 + groupOrder?: number; 89 + }, 90 + ) { 91 + // Get monitor data for name and workspace_id 92 + const monitorData = await db 93 + .select({ 94 + id: monitor.id, 95 + name: monitor.name, 96 + externalName: monitor.externalName, 97 + workspaceId: monitor.workspaceId, 98 + }) 99 + .from(monitor) 100 + .where(eq(monitor.id, data.monitorId)) 101 + .get(); 102 + 103 + if (!monitorData || !monitorData.workspaceId) return; 104 + 105 + await db 106 + .insert(pageComponent) 107 + .values({ 108 + workspaceId: monitorData.workspaceId, 109 + pageId: data.pageId, 110 + type: "monitor", 111 + monitorId: data.monitorId, 112 + name: monitorData.externalName || monitorData.name, 113 + order: data.order ?? 0, 114 + groupId: data.monitorGroupId ?? null, 115 + groupOrder: data.groupOrder ?? 0, 116 + }) 117 + .onConflictDoNothing(); 118 + } 119 + 120 + /** 121 + * Syncs multiple monitors_to_pages inserts to page_component 122 + */ 123 + export async function syncMonitorsToPageInsertMany( 124 + db: DB | Transaction, 125 + items: Array<{ 126 + monitorId: number; 127 + pageId: number; 128 + order?: number; 129 + monitorGroupId?: number | null; 130 + groupOrder?: number; 131 + }>, 132 + ) { 133 + if (items.length === 0) return; 134 + 135 + // Get all monitor data in one query 136 + const monitorIds = [...new Set(items.map((item) => item.monitorId))]; 137 + const monitors = await db 138 + .select({ 139 + id: monitor.id, 140 + name: monitor.name, 141 + externalName: monitor.externalName, 142 + workspaceId: monitor.workspaceId, 143 + }) 144 + .from(monitor) 145 + .where(inArray(monitor.id, monitorIds)); 146 + 147 + const monitorMap = new Map(monitors.map((m) => [m.id, m])); 148 + 149 + const values = items 150 + .map((item) => { 151 + const m = monitorMap.get(item.monitorId); 152 + if (!m || !m.workspaceId) return null; 153 + return { 154 + workspaceId: m.workspaceId, 155 + pageId: item.pageId, 156 + type: "monitor" as const, 157 + monitorId: item.monitorId, 158 + name: m.externalName || m.name, 159 + order: item.order ?? 0, 160 + groupId: item.monitorGroupId ?? null, 161 + groupOrder: item.groupOrder ?? 0, 162 + }; 163 + }) 164 + .filter((v): v is NonNullable<typeof v> => v !== null); 165 + 166 + if (values.length === 0) return; 167 + 168 + await db.insert(pageComponent).values(values).onConflictDoNothing(); 169 + } 170 + 171 + /** 172 + * Syncs a monitors_to_pages delete to page_component 173 + */ 174 + export async function syncMonitorsToPageDelete( 175 + db: DB | Transaction, 176 + data: { monitorId: number; pageId: number }, 177 + ) { 178 + await db 179 + .delete(pageComponent) 180 + .where( 181 + and( 182 + eq(pageComponent.monitorId, data.monitorId), 183 + eq(pageComponent.pageId, data.pageId), 184 + ), 185 + ); 186 + } 187 + 188 + /** 189 + * Syncs monitors_to_pages deletes for a specific page to page_component 190 + */ 191 + export async function syncMonitorsToPageDeleteByPage( 192 + db: DB | Transaction, 193 + pageId: number, 194 + ) { 195 + await db 196 + .delete(pageComponent) 197 + .where( 198 + and(eq(pageComponent.pageId, pageId), eq(pageComponent.type, "monitor")), 199 + ); 200 + } 201 + 202 + /** 203 + * Syncs monitors_to_pages deletes for specific monitors to page_component 204 + */ 205 + export async function syncMonitorsToPageDeleteByMonitors( 206 + db: DB | Transaction, 207 + monitorIds: number[], 208 + ) { 209 + if (monitorIds.length === 0) return; 210 + await db 211 + .delete(pageComponent) 212 + .where(inArray(pageComponent.monitorId, monitorIds)); 213 + } 214 + 215 + // ============================================================================ 216 + // Status Report to Monitors <-> Status Report to Page Components Sync 217 + // ============================================================================ 218 + 219 + /** 220 + * Syncs a status_report_to_monitors insert to status_report_to_page_component 221 + */ 222 + export async function syncStatusReportToMonitorInsert( 223 + db: DB | Transaction, 224 + data: { statusReportId: number; monitorId: number }, 225 + ) { 226 + // Get the status report's page_id 227 + const report = await db 228 + .select({ pageId: statusReport.pageId }) 229 + .from(statusReport) 230 + .where(eq(statusReport.id, data.statusReportId)) 231 + .get(); 232 + 233 + // Find matching page_components 234 + const components = await db 235 + .select({ id: pageComponent.id }) 236 + .from(pageComponent) 237 + .where( 238 + and( 239 + eq(pageComponent.monitorId, data.monitorId), 240 + report?.pageId 241 + ? eq(pageComponent.pageId, report.pageId) 242 + : // If no page_id on status report, match all page_components with this monitor 243 + eq(pageComponent.monitorId, data.monitorId), 244 + ), 245 + ); 246 + 247 + if (components.length === 0) return; 248 + 249 + await db 250 + .insert(statusReportsToPageComponents) 251 + .values( 252 + components.map((c) => ({ 253 + statusReportId: data.statusReportId, 254 + pageComponentId: c.id, 255 + })), 256 + ) 257 + .onConflictDoNothing(); 258 + } 259 + 260 + /** 261 + * Syncs multiple status_report_to_monitors inserts to status_report_to_page_component 262 + */ 263 + export async function syncStatusReportToMonitorInsertMany( 264 + db: DB | Transaction, 265 + statusReportId: number, 266 + monitorIds: number[], 267 + ) { 268 + if (monitorIds.length === 0) return; 269 + 270 + // Get the status report's page_id 271 + const report = await db 272 + .select({ pageId: statusReport.pageId }) 273 + .from(statusReport) 274 + .where(eq(statusReport.id, statusReportId)) 275 + .get(); 276 + 277 + // Find matching page_components for all monitors 278 + const components = await db 279 + .select({ id: pageComponent.id, monitorId: pageComponent.monitorId }) 280 + .from(pageComponent) 281 + .where( 282 + and( 283 + inArray(pageComponent.monitorId, monitorIds), 284 + report?.pageId 285 + ? eq(pageComponent.pageId, report.pageId) 286 + : // If no page_id, we need to be careful - get components that match the monitor 287 + inArray(pageComponent.monitorId, monitorIds), 288 + ), 289 + ); 290 + 291 + if (components.length === 0) return; 292 + 293 + await db 294 + .insert(statusReportsToPageComponents) 295 + .values( 296 + components.map((c) => ({ 297 + statusReportId, 298 + pageComponentId: c.id, 299 + })), 300 + ) 301 + .onConflictDoNothing(); 302 + } 303 + 304 + /** 305 + * Syncs a status_report_to_monitors delete to status_report_to_page_component 306 + */ 307 + export async function syncStatusReportToMonitorDelete( 308 + db: DB | Transaction, 309 + data: { statusReportId: number; monitorId: number }, 310 + ) { 311 + // Find page_components with this monitor 312 + const components = await db 313 + .select({ id: pageComponent.id }) 314 + .from(pageComponent) 315 + .where(eq(pageComponent.monitorId, data.monitorId)); 316 + 317 + if (components.length === 0) return; 318 + 319 + await db.delete(statusReportsToPageComponents).where( 320 + and( 321 + eq(statusReportsToPageComponents.statusReportId, data.statusReportId), 322 + inArray( 323 + statusReportsToPageComponents.pageComponentId, 324 + components.map((c) => c.id), 325 + ), 326 + ), 327 + ); 328 + } 329 + 330 + /** 331 + * Syncs status_report_to_monitors deletes for a specific status report 332 + */ 333 + export async function syncStatusReportToMonitorDeleteByStatusReport( 334 + db: DB | Transaction, 335 + statusReportId: number, 336 + ) { 337 + await db 338 + .delete(statusReportsToPageComponents) 339 + .where(eq(statusReportsToPageComponents.statusReportId, statusReportId)); 340 + } 341 + 342 + /** 343 + * Syncs status_report_to_monitors deletes for specific monitors 344 + */ 345 + export async function syncStatusReportToMonitorDeleteByMonitors( 346 + db: DB | Transaction, 347 + monitorIds: number[], 348 + ) { 349 + if (monitorIds.length === 0) return; 350 + 351 + // Find page_components with these monitors 352 + const components = await db 353 + .select({ id: pageComponent.id }) 354 + .from(pageComponent) 355 + .where(inArray(pageComponent.monitorId, monitorIds)); 356 + 357 + if (components.length === 0) return; 358 + 359 + await db.delete(statusReportsToPageComponents).where( 360 + inArray( 361 + statusReportsToPageComponents.pageComponentId, 362 + components.map((c) => c.id), 363 + ), 364 + ); 365 + } 366 + 367 + // ============================================================================ 368 + // Maintenance to Monitor <-> Maintenance to Page Component Sync 369 + // ============================================================================ 370 + 371 + /** 372 + * Syncs a maintenance_to_monitor insert to maintenance_to_page_component 373 + */ 374 + export async function syncMaintenanceToMonitorInsert( 375 + db: DB | Transaction, 376 + data: { maintenanceId: number; monitorId: number }, 377 + ) { 378 + // Get the maintenance's page_id 379 + const maint = await db 380 + .select({ pageId: maintenance.pageId }) 381 + .from(maintenance) 382 + .where(eq(maintenance.id, data.maintenanceId)) 383 + .get(); 384 + 385 + // Find matching page_components 386 + const components = await db 387 + .select({ id: pageComponent.id }) 388 + .from(pageComponent) 389 + .where( 390 + and( 391 + eq(pageComponent.monitorId, data.monitorId), 392 + maint?.pageId 393 + ? eq(pageComponent.pageId, maint.pageId) 394 + : eq(pageComponent.monitorId, data.monitorId), 395 + ), 396 + ); 397 + 398 + if (components.length === 0) return; 399 + 400 + await db 401 + .insert(maintenancesToPageComponents) 402 + .values( 403 + components.map((c) => ({ 404 + maintenanceId: data.maintenanceId, 405 + pageComponentId: c.id, 406 + })), 407 + ) 408 + .onConflictDoNothing(); 409 + } 410 + 411 + /** 412 + * Syncs multiple maintenance_to_monitor inserts to maintenance_to_page_component 413 + */ 414 + export async function syncMaintenanceToMonitorInsertMany( 415 + db: DB | Transaction, 416 + maintenanceId: number, 417 + monitorIds: number[], 418 + ) { 419 + if (monitorIds.length === 0) return; 420 + 421 + // Get the maintenance's page_id 422 + const maint = await db 423 + .select({ pageId: maintenance.pageId }) 424 + .from(maintenance) 425 + .where(eq(maintenance.id, maintenanceId)) 426 + .get(); 427 + 428 + // Find matching page_components for all monitors 429 + const components = await db 430 + .select({ id: pageComponent.id, monitorId: pageComponent.monitorId }) 431 + .from(pageComponent) 432 + .where( 433 + and( 434 + inArray(pageComponent.monitorId, monitorIds), 435 + maint?.pageId 436 + ? eq(pageComponent.pageId, maint.pageId) 437 + : inArray(pageComponent.monitorId, monitorIds), 438 + ), 439 + ); 440 + 441 + if (components.length === 0) return; 442 + 443 + await db 444 + .insert(maintenancesToPageComponents) 445 + .values( 446 + components.map((c) => ({ 447 + maintenanceId, 448 + pageComponentId: c.id, 449 + })), 450 + ) 451 + .onConflictDoNothing(); 452 + } 453 + 454 + /** 455 + * Syncs a maintenance_to_monitor delete to maintenance_to_page_component 456 + */ 457 + export async function syncMaintenanceToMonitorDelete( 458 + db: DB | Transaction, 459 + data: { maintenanceId: number; monitorId: number }, 460 + ) { 461 + // Find page_components with this monitor 462 + const components = await db 463 + .select({ id: pageComponent.id }) 464 + .from(pageComponent) 465 + .where(eq(pageComponent.monitorId, data.monitorId)); 466 + 467 + if (components.length === 0) return; 468 + 469 + await db.delete(maintenancesToPageComponents).where( 470 + and( 471 + eq(maintenancesToPageComponents.maintenanceId, data.maintenanceId), 472 + inArray( 473 + maintenancesToPageComponents.pageComponentId, 474 + components.map((c) => c.id), 475 + ), 476 + ), 477 + ); 478 + } 479 + 480 + /** 481 + * Syncs maintenance_to_monitor deletes for a specific maintenance 482 + */ 483 + export async function syncMaintenanceToMonitorDeleteByMaintenance( 484 + db: DB | Transaction, 485 + maintenanceId: number, 486 + ) { 487 + await db 488 + .delete(maintenancesToPageComponents) 489 + .where(eq(maintenancesToPageComponents.maintenanceId, maintenanceId)); 490 + } 491 + 492 + /** 493 + * Syncs maintenance_to_monitor deletes for specific monitors 494 + */ 495 + export async function syncMaintenanceToMonitorDeleteByMonitors( 496 + db: DB | Transaction, 497 + monitorIds: number[], 498 + ) { 499 + if (monitorIds.length === 0) return; 500 + 501 + // Find page_components with these monitors 502 + const components = await db 503 + .select({ id: pageComponent.id }) 504 + .from(pageComponent) 505 + .where(inArray(pageComponent.monitorId, monitorIds)); 506 + 507 + if (components.length === 0) return; 508 + 509 + await db.delete(maintenancesToPageComponents).where( 510 + inArray( 511 + maintenancesToPageComponents.pageComponentId, 512 + components.map((c) => c.id), 513 + ), 514 + ); 515 + }