From 2055c4eb4747e213432c484f21685b32400b4a9c Mon Sep 17 00:00:00 2001 From: Maximilian Kaske <56969857+mxkaske@users.noreply.github.com> Date: Thu, 5 Feb 2026 15:08:50 +0100 Subject: [PATCH] chore: remove sync monitors table (#1829) * chore: remove sync monitors table * fix: remaining sync * fix: test * chore: remove legacy stuff --- .../src/routes/v1/__test__/sync.test.ts | 573 ------------- .../server/src/routes/v1/maintenances/post.ts | 19 +- apps/server/src/routes/v1/maintenances/put.ts | 34 +- apps/server/src/routes/v1/pages/post.test.ts | 33 +- apps/server/src/routes/v1/pages/post.ts | 26 +- apps/server/src/routes/v1/pages/put.ts | 37 +- .../src/routes/v1/statusReports/post.test.ts | 50 +- .../src/routes/v1/statusReports/post.ts | 19 +- packages/api/src/router/maintenance.ts | 29 +- packages/api/src/router/monitor.ts | 24 +- packages/api/src/router/page.ts | 24 +- packages/api/src/router/statusPage.utils.ts | 96 --- packages/api/src/router/statusReport.ts | 28 +- packages/api/src/router/sync.test.ts | 506 ------------ packages/db/src/index.ts | 1 - .../db/src/schema/maintenances/maintenance.ts | 9 +- packages/db/src/schema/monitors/monitor.ts | 5 + .../schema/status_reports/status_reports.ts | 9 +- packages/db/src/seed.mts | 31 - packages/db/src/sync.ts | 760 ------------------ 20 files changed, 54 insertions(+), 2259 deletions(-) delete mode 100644 apps/server/src/routes/v1/__test__/sync.test.ts delete mode 100644 packages/api/src/router/sync.test.ts delete mode 100644 packages/db/src/sync.ts diff --git a/apps/server/src/routes/v1/__test__/sync.test.ts b/apps/server/src/routes/v1/__test__/sync.test.ts deleted file mode 100644 index 692653d1f..000000000 --- a/apps/server/src/routes/v1/__test__/sync.test.ts +++ /dev/null @@ -1,573 +0,0 @@ -import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { and, db, eq, inArray } from "@openstatus/db"; -import { - maintenance, - maintenancesToMonitors, - maintenancesToPageComponents, - monitor, - monitorsToPages, - monitorsToStatusReport, - page, - pageComponent, - statusReport, - statusReportUpdate, - statusReportsToPageComponents, -} from "@openstatus/db/src/schema"; - -import { app } from "@/index"; - -/** - * Sync Tests for REST API: Verify that mutations to legacy tables also sync to new page_component tables - * - * These tests use the REST API endpoints and verify that the sync operations - * correctly update the new page_component related tables. - */ - -const TEST_PREFIX = "api-sync-test"; -let testPageId: number; -let testMonitorId: number; - -beforeAll(async () => { - // Clean up any existing test data - await db - .delete(pageComponent) - .where(eq(pageComponent.name, `${TEST_PREFIX}-monitor`)); - await db.delete(page).where(eq(page.slug, `${TEST_PREFIX}-page`)); - await db.delete(monitor).where(eq(monitor.name, `${TEST_PREFIX}-monitor`)); - - // Create test page - const testPage = await db - .insert(page) - .values({ - workspaceId: 1, - title: "API Sync Test Page", - description: "A test page for API sync tests", - slug: `${TEST_PREFIX}-page`, - customDomain: "", - }) - .returning() - .get(); - testPageId = testPage.id; - - // Create test monitor - const testMonitor = await db - .insert(monitor) - .values({ - workspaceId: 1, - name: `${TEST_PREFIX}-monitor`, - url: "https://api-sync-test.example.com", - periodicity: "1m", - active: true, - regions: "ams", - jobType: "http", - }) - .returning() - .get(); - testMonitorId = testMonitor.id; - - // Add monitor to page (creates monitors_to_pages entry) - await db.insert(monitorsToPages).values({ - monitorId: testMonitorId, - pageId: testPageId, - }); - - // Create page_component for the monitor (simulating initial sync) - await db - .insert(pageComponent) - .values({ - workspaceId: 1, - pageId: testPageId, - monitorId: testMonitorId, - type: "monitor", - name: `${TEST_PREFIX}-monitor`, - }) - .onConflictDoNothing(); -}); - -afterAll(async () => { - // Clean up test data in correct order - await db - .delete(maintenancesToPageComponents) - .where( - inArray( - maintenancesToPageComponents.pageComponentId, - db - .select({ id: pageComponent.id }) - .from(pageComponent) - .where(eq(pageComponent.pageId, testPageId)), - ), - ); - await db - .delete(statusReportsToPageComponents) - .where( - inArray( - statusReportsToPageComponents.pageComponentId, - db - .select({ id: pageComponent.id }) - .from(pageComponent) - .where(eq(pageComponent.pageId, testPageId)), - ), - ); - await db.delete(pageComponent).where(eq(pageComponent.pageId, testPageId)); - await db - .delete(monitorsToPages) - .where(eq(monitorsToPages.pageId, testPageId)); - await db - .delete(maintenancesToMonitors) - .where(eq(maintenancesToMonitors.monitorId, testMonitorId)); - await db - .delete(monitorsToStatusReport) - .where(eq(monitorsToStatusReport.monitorId, testMonitorId)); - await db - .delete(statusReportUpdate) - .where( - inArray( - statusReportUpdate.statusReportId, - db - .select({ id: statusReport.id }) - .from(statusReport) - .where(eq(statusReport.pageId, testPageId)), - ), - ); - await db.delete(statusReport).where(eq(statusReport.pageId, testPageId)); - await db.delete(maintenance).where(eq(maintenance.pageId, testPageId)); - await db.delete(page).where(eq(page.slug, `${TEST_PREFIX}-page`)); - await db.delete(monitor).where(eq(monitor.name, `${TEST_PREFIX}-monitor`)); -}); - -describe("API Sync: POST /v1/maintenance - maintenance_to_page_component", () => { - let createdMaintenanceId: number; - - afterAll(async () => { - if (createdMaintenanceId) { - await db - .delete(maintenancesToPageComponents) - .where( - eq(maintenancesToPageComponents.maintenanceId, createdMaintenanceId), - ); - await db - .delete(maintenancesToMonitors) - .where(eq(maintenancesToMonitors.maintenanceId, createdMaintenanceId)); - await db - .delete(maintenance) - .where(eq(maintenance.id, createdMaintenanceId)); - } - }); - - test("creating maintenance with monitors creates maintenance_to_page_component and reverse syncs to maintenance_to_monitor", async () => { - const from = new Date(); - const to = new Date(from.getTime() + 3600000); - - const res = await app.request("/v1/maintenance", { - method: "POST", - headers: { - "x-openstatus-key": "1", - "content-type": "application/json", - }, - body: JSON.stringify({ - title: `${TEST_PREFIX} Maintenance`, - message: "Test maintenance for sync", - from: from.toISOString(), - to: to.toISOString(), - monitorIds: [testMonitorId], - pageId: testPageId, - }), - }); - - expect(res.status).toBe(200); - - const data = await res.json(); - createdMaintenanceId = data.id; - - // Verify maintenance_to_page_component was created FIRST (primary source) - const component = await db.query.pageComponent.findFirst({ - where: and( - eq(pageComponent.monitorId, testMonitorId), - eq(pageComponent.pageId, testPageId), - ), - }); - - expect(component).toBeDefined(); - - const maintenanceToComponent = - await db.query.maintenancesToPageComponents.findFirst({ - where: and( - eq(maintenancesToPageComponents.maintenanceId, createdMaintenanceId), - eq(maintenancesToPageComponents.pageComponentId, component?.id), - ), - }); - expect(maintenanceToComponent).toBeDefined(); - - // Verify maintenance_to_monitor was reverse synced (for backward compatibility) - const maintenanceToMonitor = - await db.query.maintenancesToMonitors.findFirst({ - where: and( - eq(maintenancesToMonitors.maintenanceId, createdMaintenanceId), - eq(maintenancesToMonitors.monitorId, testMonitorId), - ), - }); - expect(maintenanceToMonitor).toBeDefined(); - }); -}); - -describe("API Sync: PUT /v1/maintenance - maintenance_to_page_component updates", () => { - let testMaintenanceId: number; - - beforeAll(async () => { - // Create a maintenance for update tests - const from = new Date(); - const to = new Date(from.getTime() + 3600000); - - const newMaintenance = await db - .insert(maintenance) - .values({ - workspaceId: 1, - pageId: testPageId, - title: `${TEST_PREFIX} Update Test Maintenance`, - message: "For update tests", - from, - to, - }) - .returning() - .get(); - testMaintenanceId = newMaintenance.id; - - // Add monitor to maintenance - await db.insert(maintenancesToMonitors).values({ - maintenanceId: testMaintenanceId, - monitorId: testMonitorId, - }); - - // Sync to page component - const component = await db.query.pageComponent.findFirst({ - where: and( - eq(pageComponent.monitorId, testMonitorId), - eq(pageComponent.pageId, testPageId), - ), - }); - - if (component) { - await db - .insert(maintenancesToPageComponents) - .values({ - maintenanceId: testMaintenanceId, - pageComponentId: component.id, - }) - .onConflictDoNothing(); - } - }); - - afterAll(async () => { - if (testMaintenanceId) { - await db - .delete(maintenancesToPageComponents) - .where( - eq(maintenancesToPageComponents.maintenanceId, testMaintenanceId), - ); - await db - .delete(maintenancesToMonitors) - .where(eq(maintenancesToMonitors.maintenanceId, testMaintenanceId)); - await db.delete(maintenance).where(eq(maintenance.id, testMaintenanceId)); - } - }); - - test("removing monitors from maintenance deletes from maintenance_to_page_component and reverse syncs delete to maintenance_to_monitor", async () => { - const res = await app.request(`/v1/maintenance/${testMaintenanceId}`, { - method: "PUT", - headers: { - "x-openstatus-key": "1", - "content-type": "application/json", - }, - body: JSON.stringify({ - monitorIds: [], - }), - }); - - expect(res.status).toBe(200); - - // Verify maintenance_to_page_component was deleted FIRST (primary source) - const maintenanceToComponent = - await db.query.maintenancesToPageComponents.findFirst({ - where: eq( - maintenancesToPageComponents.maintenanceId, - testMaintenanceId, - ), - }); - expect(maintenanceToComponent).toBeUndefined(); - - // Verify maintenance_to_monitor was reverse synced (deleted for backward compatibility) - const maintenanceToMonitor = - await db.query.maintenancesToMonitors.findFirst({ - where: and( - eq(maintenancesToMonitors.maintenanceId, testMaintenanceId), - eq(maintenancesToMonitors.monitorId, testMonitorId), - ), - }); - expect(maintenanceToMonitor).toBeUndefined(); - }); -}); - -describe("API Sync: POST /v1/status_report - status_report_to_page_component", () => { - let createdStatusReportId: number; - - afterAll(async () => { - if (createdStatusReportId) { - await db - .delete(statusReportsToPageComponents) - .where( - eq( - statusReportsToPageComponents.statusReportId, - createdStatusReportId, - ), - ); - await db - .delete(monitorsToStatusReport) - .where( - eq(monitorsToStatusReport.statusReportId, createdStatusReportId), - ); - await db - .delete(statusReportUpdate) - .where(eq(statusReportUpdate.statusReportId, createdStatusReportId)); - await db - .delete(statusReport) - .where(eq(statusReport.id, createdStatusReportId)); - } - }); - - test("creating status report with monitors syncs to status_report_to_page_component", async () => { - const res = await app.request("/v1/status_report", { - method: "POST", - headers: { - "x-openstatus-key": "1", - "content-type": "application/json", - }, - body: JSON.stringify({ - title: `${TEST_PREFIX} Status Report`, - status: "investigating", - message: "Test status report for sync", - monitorIds: [testMonitorId], - pageId: testPageId, - }), - }); - - expect(res.status).toBe(200); - - const data = await res.json(); - createdStatusReportId = data.id; - - // Verify status_report_to_monitors was created - const reportToMonitor = await db.query.monitorsToStatusReport.findFirst({ - where: and( - eq(monitorsToStatusReport.statusReportId, createdStatusReportId), - eq(monitorsToStatusReport.monitorId, testMonitorId), - ), - }); - expect(reportToMonitor).toBeDefined(); - - // Verify status_report_to_page_component was synced - const component = await db.query.pageComponent.findFirst({ - where: and( - eq(pageComponent.monitorId, testMonitorId), - eq(pageComponent.pageId, testPageId), - ), - }); - - if (component) { - const reportToComponent = - await db.query.statusReportsToPageComponents.findFirst({ - where: and( - eq( - statusReportsToPageComponents.statusReportId, - createdStatusReportId, - ), - eq(statusReportsToPageComponents.pageComponentId, component.id), - ), - }); - expect(reportToComponent).toBeDefined(); - } - }); -}); - -describe("API Sync: POST /v1/page - monitors_to_pages -> page_component", () => { - let createdPageId: number; - - afterAll(async () => { - if (createdPageId) { - await db - .delete(pageComponent) - .where(eq(pageComponent.pageId, createdPageId)); - await db - .delete(monitorsToPages) - .where(eq(monitorsToPages.pageId, createdPageId)); - await db.delete(page).where(eq(page.id, createdPageId)); - } - }); - - test("creating page with monitors syncs to page_component", async () => { - const res = await app.request("/v1/page", { - method: "POST", - headers: { - "x-openstatus-key": "1", - "content-type": "application/json", - }, - body: JSON.stringify({ - title: `${TEST_PREFIX} New Page`, - description: "Test page for sync", - slug: `${TEST_PREFIX}-new-page`, - monitors: [testMonitorId], - }), - }); - - expect(res.status).toBe(200); - - const data = await res.json(); - createdPageId = data.id; - - // Verify monitors_to_pages was created - const monitorToPage = await db.query.monitorsToPages.findFirst({ - where: and( - eq(monitorsToPages.monitorId, testMonitorId), - eq(monitorsToPages.pageId, createdPageId), - ), - }); - expect(monitorToPage).toBeDefined(); - - // Verify page_component was synced - const component = await db.query.pageComponent.findFirst({ - where: and( - eq(pageComponent.monitorId, testMonitorId), - eq(pageComponent.pageId, createdPageId), - ), - }); - expect(component).toBeDefined(); - expect(component?.type).toBe("monitor"); - }); -}); - -describe("API Sync: PUT /v1/page - monitors_to_pages -> page_component updates", () => { - let updateTestPageId: number; - - beforeAll(async () => { - // Create a page for update tests - const updateTestPage = await db - .insert(page) - .values({ - workspaceId: 1, - title: `${TEST_PREFIX} Update Test Page`, - description: "For update tests", - slug: `${TEST_PREFIX}-update-page`, - customDomain: "", - }) - .returning() - .get(); - updateTestPageId = updateTestPage.id; - }); - - afterAll(async () => { - if (updateTestPageId) { - await db - .delete(pageComponent) - .where(eq(pageComponent.pageId, updateTestPageId)); - await db - .delete(monitorsToPages) - .where(eq(monitorsToPages.pageId, updateTestPageId)); - await db.delete(page).where(eq(page.id, updateTestPageId)); - } - }); - - test("adding monitors to page syncs to page_component", async () => { - const res = await app.request(`/v1/page/${updateTestPageId}`, { - method: "PUT", - headers: { - "x-openstatus-key": "1", - "content-type": "application/json", - }, - body: JSON.stringify({ - monitors: [testMonitorId], - }), - }); - - expect(res.status).toBe(200); - - // Verify monitors_to_pages was created - const monitorToPage = await db.query.monitorsToPages.findFirst({ - where: and( - eq(monitorsToPages.monitorId, testMonitorId), - eq(monitorsToPages.pageId, updateTestPageId), - ), - }); - expect(monitorToPage).toBeDefined(); - - // Verify page_component was synced - const component = await db.query.pageComponent.findFirst({ - where: and( - eq(pageComponent.monitorId, testMonitorId), - eq(pageComponent.pageId, updateTestPageId), - ), - }); - expect(component).toBeDefined(); - }); - - test("removing monitors from page syncs delete to page_component", async () => { - // First ensure monitor is on the page - await db - .insert(monitorsToPages) - .values({ - monitorId: testMonitorId, - pageId: updateTestPageId, - }) - .onConflictDoNothing(); - - await db - .insert(pageComponent) - .values({ - workspaceId: 1, - pageId: updateTestPageId, - monitorId: testMonitorId, - type: "monitor", - name: `${TEST_PREFIX}-monitor`, - }) - .onConflictDoNothing(); - - // Verify page_component exists before removal - let component = await db.query.pageComponent.findFirst({ - where: and( - eq(pageComponent.monitorId, testMonitorId), - eq(pageComponent.pageId, updateTestPageId), - ), - }); - expect(component).toBeDefined(); - - // Remove all monitors - const res = await app.request(`/v1/page/${updateTestPageId}`, { - method: "PUT", - headers: { - "x-openstatus-key": "1", - "content-type": "application/json", - }, - body: JSON.stringify({ - monitors: [], - }), - }); - - expect(res.status).toBe(200); - - // Verify monitors_to_pages was deleted - const monitorToPage = await db.query.monitorsToPages.findFirst({ - where: and( - eq(monitorsToPages.monitorId, testMonitorId), - eq(monitorsToPages.pageId, updateTestPageId), - ), - }); - expect(monitorToPage).toBeUndefined(); - - // Verify page_component was deleted - component = await db.query.pageComponent.findFirst({ - where: and( - eq(pageComponent.monitorId, testMonitorId), - eq(pageComponent.pageId, updateTestPageId), - ), - }); - expect(component).toBeUndefined(); - }); -}); diff --git a/apps/server/src/routes/v1/maintenances/post.ts b/apps/server/src/routes/v1/maintenances/post.ts index a054bb07f..20053fbec 100644 --- a/apps/server/src/routes/v1/maintenances/post.ts +++ b/apps/server/src/routes/v1/maintenances/post.ts @@ -3,15 +3,7 @@ import { OpenStatusApiError, openApiErrorResponses } from "@/libs/errors"; import { trackMiddleware } from "@/libs/middlewares"; import { createRoute } from "@hono/zod-openapi"; import { Events } from "@openstatus/analytics"; -import { - and, - db, - eq, - inArray, - isNotNull, - isNull, - syncMaintenanceToPageComponentInsertMany, -} from "@openstatus/db"; +import { and, db, eq, inArray, isNotNull, isNull } from "@openstatus/db"; import { monitor, page, pageSubscriber } from "@openstatus/db/src/schema"; import { maintenance } from "@openstatus/db/src/schema/maintenances"; import { @@ -123,7 +115,7 @@ export function registerPostMaintenance(api: typeof maintenancesApi) { .all(); if (pageComponents.length > 0) { - // Insert to maintenancesToPageComponents FIRST (new primary source) + // Insert to maintenancesToPageComponents await tx .insert(maintenancesToPageComponents) .values( @@ -133,13 +125,6 @@ export function registerPostMaintenance(api: typeof maintenancesApi) { })), ) .run(); - - // Reverse sync to maintenancesToMonitors (for backward compatibility) - await syncMaintenanceToPageComponentInsertMany( - tx, - newMaintenance.id, - pageComponents.map((pc) => pc.id), - ); } } diff --git a/apps/server/src/routes/v1/maintenances/put.ts b/apps/server/src/routes/v1/maintenances/put.ts index 9130dcfe1..23800a39e 100644 --- a/apps/server/src/routes/v1/maintenances/put.ts +++ b/apps/server/src/routes/v1/maintenances/put.ts @@ -2,15 +2,7 @@ import { OpenStatusApiError, openApiErrorResponses } from "@/libs/errors"; import { trackMiddleware } from "@/libs/middlewares"; import { createRoute } from "@hono/zod-openapi"; import { Events } from "@openstatus/analytics"; -import { - and, - db, - eq, - inArray, - isNull, - syncMaintenanceToPageComponentDeleteByMaintenance, - syncMaintenanceToPageComponentInsertMany, -} from "@openstatus/db"; +import { and, db, eq, inArray, isNull } from "@openstatus/db"; import { monitor, page } from "@openstatus/db/src/schema"; import { maintenance } from "@openstatus/db/src/schema/maintenances"; import { @@ -59,7 +51,11 @@ export function registerPutMaintenance(api: typeof maintenancesApi) { const _maintenance = await db.query.maintenance.findFirst({ with: { - maintenancesToMonitors: true, + maintenancesToPageComponents: { + with: { + pageComponent: true, + }, + }, }, where: and( eq(maintenance.id, Number(id)), @@ -132,15 +128,12 @@ export function registerPutMaintenance(api: typeof maintenancesApi) { .get(); if (monitorIds) { - // Delete from maintenancesToPageComponents FIRST (new primary source) + // Delete from maintenancesToPageComponents await tx .delete(maintenancesToPageComponents) .where(eq(maintenancesToPageComponents.maintenanceId, Number(id))) .run(); - // Reverse sync delete to maintenancesToMonitors (for backward compatibility) - await syncMaintenanceToPageComponentDeleteByMaintenance(tx, Number(id)); - // Add new associations if (monitorIds.length > 0 && updated.pageId) { // Get page components for the new monitors @@ -156,7 +149,7 @@ export function registerPutMaintenance(api: typeof maintenancesApi) { .all(); if (pageComponents.length > 0) { - // Insert to maintenancesToPageComponents FIRST + // Insert to maintenancesToPageComponents await tx .insert(maintenancesToPageComponents) .values( @@ -166,13 +159,6 @@ export function registerPutMaintenance(api: typeof maintenancesApi) { })), ) .run(); - - // Reverse sync to maintenancesToMonitors - await syncMaintenanceToPageComponentInsertMany( - tx, - Number(id), - pageComponents.map((pc) => pc.id), - ); } } } @@ -184,7 +170,9 @@ export function registerPutMaintenance(api: typeof maintenancesApi) { ...updatedMaintenance, monitorIds: monitorIds ?? - _maintenance.maintenancesToMonitors.map((m) => m.monitorId), + _maintenance.maintenancesToPageComponents + .map((m) => m.pageComponent.monitorId) + .filter((id): id is number => id !== null), }); return c.json(data, 200); diff --git a/apps/server/src/routes/v1/pages/post.test.ts b/apps/server/src/routes/v1/pages/post.test.ts index a97589fe4..5d3e4c70b 100644 --- a/apps/server/src/routes/v1/pages/post.test.ts +++ b/apps/server/src/routes/v1/pages/post.test.ts @@ -2,12 +2,7 @@ import { expect, test } from "bun:test"; import { app } from "@/index"; import { db, eq } from "@openstatus/db"; -import { - monitor, - monitorsToPages, - page, - pageComponent, -} from "@openstatus/db/src/schema"; +import { monitor, page, pageComponent } from "@openstatus/db/src/schema"; import { PageSchema } from "./schema"; test("create a valid page", async () => { @@ -282,17 +277,6 @@ test("create a page with monitors as objects with order", async () => { expect(components.find((c) => c.monitorId === 1)?.order).toBe(1); expect(components.find((c) => c.monitorId === 2)?.order).toBe(0); - // Verify sync to legacy table - const legacyEntries = await db - .select() - .from(monitorsToPages) - .where(eq(monitorsToPages.pageId, result.data.id)) - .all(); - - expect(legacyEntries.length).toBe(2); - expect(legacyEntries.find((e) => e.monitorId === 1)?.order).toBe(1); - expect(legacyEntries.find((e) => e.monitorId === 2)?.order).toBe(0); - // Cleanup await db.delete(page).where(eq(page.id, result.data.id)); } @@ -389,7 +373,7 @@ test("create a page with partial invalid monitors should return 400", async () = expect(json.message).toContain("not found"); }); -test("create a page syncs correctly to pageComponent and legacy monitorsToPages", async () => { +test("create a page syncs correctly to pageComponent", async () => { const uniqueSlug = `sync-test-${Date.now()}`; const res = await app.request("/v1/page", { method: "POST", @@ -421,16 +405,6 @@ test("create a page syncs correctly to pageComponent and legacy monitorsToPages" expect(components[0].monitorId).toBe(1); expect(components[0].type).toBe("monitor"); - // Verify monitorsToPages (legacy table) - const legacyEntries = await db - .select() - .from(monitorsToPages) - .where(eq(monitorsToPages.pageId, result.data.id)) - .all(); - - expect(legacyEntries.length).toBe(1); - expect(legacyEntries[0].monitorId).toBe(1); - // Cleanup await db.delete(page).where(eq(page.id, result.data.id)); } @@ -474,6 +448,9 @@ test("create a page uses monitor externalName when available", async () => { expect(components.length).toBe(1); // Should use externalName if available, otherwise name const expectedName = monitorData?.externalName || monitorData?.name; + if (!expectedName) { + throw new Error("Expected name is undefined"); + } expect(components[0].name).toBe(expectedName); // Cleanup diff --git a/apps/server/src/routes/v1/pages/post.ts b/apps/server/src/routes/v1/pages/post.ts index f93f11e3b..4ba09dc5e 100644 --- a/apps/server/src/routes/v1/pages/post.ts +++ b/apps/server/src/routes/v1/pages/post.ts @@ -1,13 +1,6 @@ import { createRoute } from "@hono/zod-openapi"; -import { - and, - eq, - inArray, - isNull, - sql, - syncPageComponentToMonitorsToPageInsertMany, -} from "@openstatus/db"; +import { and, eq, inArray, isNull, sql } from "@openstatus/db"; import { db } from "@openstatus/db/src/db"; import { monitor, @@ -214,23 +207,6 @@ export function registerPostPage(api: typeof pagesApi) { }) .run(); } - - // Sync to legacy table for backwards compatibility - const monitorsToPageValues = monitors.map((m, index) => { - const values = typeof m === "number" ? { monitorId: m } : m; - return { - pageId: _page.id, - monitorId: values.monitorId, - order: "order" in values ? values.order : index, - monitorGroupId: null, - groupOrder: 0, - }; - }); - - await syncPageComponentToMonitorsToPageInsertMany( - db, - monitorsToPageValues, - ); } const data = transformPageData(PageSchema.parse(_page)); return c.json(data, 200); diff --git a/apps/server/src/routes/v1/pages/put.ts b/apps/server/src/routes/v1/pages/put.ts index 21640126f..ae009d113 100644 --- a/apps/server/src/routes/v1/pages/put.ts +++ b/apps/server/src/routes/v1/pages/put.ts @@ -4,15 +4,7 @@ import { OpenStatusApiError, openApiErrorResponses } from "@/libs/errors"; import { trackMiddleware } from "@/libs/middlewares"; import { notEmpty } from "@/utils/not-empty"; import { Events } from "@openstatus/analytics"; -import { - and, - eq, - inArray, - isNull, - sql, - syncPageComponentToMonitorsToPageDelete, - syncPageComponentToMonitorsToPageInsertMany, -} from "@openstatus/db"; +import { and, eq, inArray, isNull, sql } from "@openstatus/db"; import { db } from "@openstatus/db/src/db"; import { monitor, @@ -185,7 +177,6 @@ export function registerPutPage(api: typeof pagesApi) { .returning() .get(); - // Query current pageComponents instead of monitorsToPages const currentPageComponents = await db .select() .from(pageComponent) @@ -200,7 +191,7 @@ export function registerPutPage(api: typeof pagesApi) { (id) => !monitorIds?.includes(id), ); - // Delete removed monitors from pageComponent (primary table) + // Delete removed monitors from pageComponent if (removedMonitorIds.length) { await db .delete(pageComponent) @@ -210,13 +201,6 @@ export function registerPutPage(api: typeof pagesApi) { eq(pageComponent.pageId, newPage.id), ), ); - // Reverse sync delete to monitorsToPages (for backwards compatibility) - for (const monitorId of removedMonitorIds) { - await syncPageComponentToMonitorsToPageDelete(db, { - monitorId, - pageId: newPage.id, - }); - } } // Insert or update pageComponents (primary table) @@ -261,23 +245,6 @@ export function registerPutPage(api: typeof pagesApi) { }) .run(); } - - // Reverse sync to monitorsToPages (for backwards compatibility) - const monitorsToPageValues = monitors.map((m, index) => { - const values = typeof m === "number" ? { monitorId: m } : m; - return { - pageId: newPage.id, - monitorId: values.monitorId, - order: "order" in values ? values.order : index, - monitorGroupId: null, - groupOrder: 0, - }; - }); - - await syncPageComponentToMonitorsToPageInsertMany( - db, - monitorsToPageValues, - ); } const data = transformPageData( diff --git a/apps/server/src/routes/v1/statusReports/post.test.ts b/apps/server/src/routes/v1/statusReports/post.test.ts index 2b36ad906..02a3c3c09 100644 --- a/apps/server/src/routes/v1/statusReports/post.test.ts +++ b/apps/server/src/routes/v1/statusReports/post.test.ts @@ -3,7 +3,6 @@ import { expect, test } from "bun:test"; import { app } from "@/index"; import { db, eq } from "@openstatus/db"; import { - monitorsToStatusReport, pageComponent, statusReport, statusReportsToPageComponents, @@ -166,7 +165,7 @@ test("no auth key should return 401", async () => { expect(res.status).toBe(401); }); -test("create a status report syncs correctly to statusReportsToPageComponents and legacy monitorsToStatusReport", async () => { +test("create a status report links correctly to statusReportsToPageComponents", async () => { const date = new Date(); date.setMilliseconds(0); @@ -179,7 +178,7 @@ test("create a status report syncs correctly to statusReportsToPageComponents an body: JSON.stringify({ status: "investigating", title: "Sync Test Status Report", - message: "Testing sync to both tables", + message: "Testing link to statusReportsToPageComponents", monitorIds: [1], date: date.toISOString(), pageId: 1, @@ -193,7 +192,7 @@ test("create a status report syncs correctly to statusReportsToPageComponents an if (result.success) { const statusReportId = result.data.id; - // Verify statusReportsToPageComponents (primary table) + // Verify statusReportsToPageComponents const components = await db .select() .from(statusReportsToPageComponents) @@ -214,16 +213,6 @@ test("create a status report syncs correctly to statusReportsToPageComponents an expect(pageComponents[0].pageId).toBe(1); expect(pageComponents[0].type).toBe("monitor"); - // Verify monitorsToStatusReport (legacy table) - const legacyEntries = await db - .select() - .from(monitorsToStatusReport) - .where(eq(monitorsToStatusReport.statusReportId, statusReportId)) - .all(); - - expect(legacyEntries.length).toBe(1); - expect(legacyEntries[0].monitorId).toBe(1); - // Cleanup await db .delete(statusReport) @@ -232,7 +221,7 @@ test("create a status report syncs correctly to statusReportsToPageComponents an } }); -test("create a status report with multiple monitors syncs correctly to both tables", async () => { +test("create a status report with multiple monitors links correctly to statusReportsToPageComponents", async () => { const date = new Date(); date.setMilliseconds(0); @@ -260,8 +249,8 @@ test("create a status report with multiple monitors syncs correctly to both tabl }, body: JSON.stringify({ status: "investigating", - title: "Multi-Monitor Sync Test", - message: "Testing sync with multiple monitors", + title: "Multi-Monitor Test", + message: "Testing with multiple monitors", monitorIds: [1, 2], date: date.toISOString(), pageId: 1, @@ -275,7 +264,7 @@ test("create a status report with multiple monitors syncs correctly to both tabl if (result.success) { const statusReportId = result.data.id; - // Verify statusReportsToPageComponents (primary table) + // Verify statusReportsToPageComponents const components = await db .select() .from(statusReportsToPageComponents) @@ -285,20 +274,6 @@ test("create a status report with multiple monitors syncs correctly to both tabl // Should only link monitors that exist as page components on this page expect(components.length).toBe(existingMonitorIds.length); - // Verify monitorsToStatusReport (legacy table) - const legacyEntries = await db - .select() - .from(monitorsToStatusReport) - .where(eq(monitorsToStatusReport.statusReportId, statusReportId)) - .all(); - - // Both tables should have the same number of entries - expect(legacyEntries.length).toBe(components.length); - - // Verify the monitor IDs in legacy table match what we expect - const legacyMonitorIds = legacyEntries.map((e) => e.monitorId).sort(); - expect(legacyMonitorIds).toEqual(existingMonitorIds.sort()); - // Cleanup await db .delete(statusReport) @@ -307,7 +282,7 @@ test("create a status report with multiple monitors syncs correctly to both tabl } }); -test("create a status report without monitorIds should not create sync entries", async () => { +test("create a status report without monitorIds should not create page component links", async () => { const date = new Date(); date.setMilliseconds(0); @@ -342,15 +317,6 @@ test("create a status report without monitorIds should not create sync entries", expect(components.length).toBe(0); - // Verify no monitorsToStatusReport entries - const legacyEntries = await db - .select() - .from(monitorsToStatusReport) - .where(eq(monitorsToStatusReport.statusReportId, statusReportId)) - .all(); - - expect(legacyEntries.length).toBe(0); - // Cleanup await db .delete(statusReport) diff --git a/apps/server/src/routes/v1/statusReports/post.ts b/apps/server/src/routes/v1/statusReports/post.ts index 0a4bd58a8..e2050dab3 100644 --- a/apps/server/src/routes/v1/statusReports/post.ts +++ b/apps/server/src/routes/v1/statusReports/post.ts @@ -1,14 +1,6 @@ import { createRoute, z } from "@hono/zod-openapi"; -import { - and, - db, - eq, - inArray, - isNotNull, - isNull, - syncStatusReportToPageComponentInsertMany, -} from "@openstatus/db"; +import { and, db, eq, inArray, isNotNull, isNull } from "@openstatus/db"; import { monitor, page, @@ -151,7 +143,7 @@ export function registerPostStatusReport(api: typeof statusReportsApi) { ) .all(); - // Insert to statusReportsToPageComponents (primary table) + // Insert to statusReportsToPageComponents if (components.length > 0) { await db .insert(statusReportsToPageComponents) @@ -162,13 +154,6 @@ export function registerPostStatusReport(api: typeof statusReportsApi) { })), ) .run(); - - // Sync to legacy table for backwards compatibility - await syncStatusReportToPageComponentInsertMany( - db, - _newStatusReport.id, - components.map((c) => c.id), - ); } } diff --git a/packages/api/src/router/maintenance.ts b/packages/api/src/router/maintenance.ts index 936650a90..1757c31b6 100644 --- a/packages/api/src/router/maintenance.ts +++ b/packages/api/src/router/maintenance.ts @@ -1,16 +1,6 @@ import { z } from "zod"; -import { - type SQL, - and, - asc, - desc, - eq, - gte, - inArray, - syncMaintenanceToPageComponentDeleteByMaintenance, - syncMaintenanceToPageComponentInsertMany, -} from "@openstatus/db"; +import { type SQL, and, asc, desc, eq, gte, inArray } from "@openstatus/db"; import { maintenance, maintenancesToPageComponents, @@ -148,12 +138,6 @@ export const maintenanceRouter = createTRPCRouter({ pageComponentId, })), ); - // Sync to monitors (inverse sync for backward compatibility) - await syncMaintenanceToPageComponentInsertMany( - tx, - newMaintenance.id, - opts.input.pageComponents, - ); } return newMaintenance; @@ -226,11 +210,6 @@ export const maintenanceRouter = createTRPCRouter({ eq(maintenancesToPageComponents.maintenanceId, _maintenance.id), ) .run(); - // Sync to monitors (inverse sync for backward compatibility) - await syncMaintenanceToPageComponentDeleteByMaintenance( - tx, - _maintenance.id, - ); // Create new relations if page components are provided if (opts.input.pageComponents?.length) { @@ -240,12 +219,6 @@ export const maintenanceRouter = createTRPCRouter({ pageComponentId, })), ); - // Sync to monitors (inverse sync for backward compatibility) - await syncMaintenanceToPageComponentInsertMany( - tx, - _maintenance.id, - opts.input.pageComponents, - ); } return _maintenance; diff --git a/packages/api/src/router/monitor.ts b/packages/api/src/router/monitor.ts index c083f63c7..f3925205e 100644 --- a/packages/api/src/router/monitor.ts +++ b/packages/api/src/router/monitor.ts @@ -14,17 +14,7 @@ import { statusAssertion, textBodyAssertion, } from "@openstatus/assertions"; -import { - type SQL, - and, - count, - eq, - inArray, - isNull, - syncMaintenanceToMonitorDeleteByMonitors, - syncMonitorsToPageDeleteByMonitors, - syncStatusReportToMonitorDeleteByMonitors, -} from "@openstatus/db"; +import { type SQL, and, count, eq, inArray, isNull } from "@openstatus/db"; import { insertMonitorSchema, maintenancesToMonitors, @@ -94,14 +84,6 @@ export const monitorRouter = createTRPCRouter({ await tx .delete(maintenancesToMonitors) .where(eq(maintenancesToMonitors.monitorId, monitorToDelete.id)); - // Sync deletes to page components - await syncMonitorsToPageDeleteByMonitors(tx, [monitorToDelete.id]); - await syncStatusReportToMonitorDeleteByMonitors(tx, [ - monitorToDelete.id, - ]); - await syncMaintenanceToMonitorDeleteByMonitors(tx, [ - monitorToDelete.id, - ]); }); }), @@ -148,10 +130,6 @@ export const monitorRouter = createTRPCRouter({ await tx .delete(maintenancesToMonitors) .where(inArray(maintenancesToMonitors.monitorId, opts.input.ids)); - // Sync deletes to page components - await syncMonitorsToPageDeleteByMonitors(tx, opts.input.ids); - await syncStatusReportToMonitorDeleteByMonitors(tx, opts.input.ids); - await syncMaintenanceToMonitorDeleteByMonitors(tx, opts.input.ids); }); }), diff --git a/packages/api/src/router/page.ts b/packages/api/src/router/page.ts index 3bf727508..14c433f51 100644 --- a/packages/api/src/router/page.ts +++ b/packages/api/src/router/page.ts @@ -1,16 +1,7 @@ import { TRPCError } from "@trpc/server"; import { z } from "zod"; -import { - type SQL, - and, - desc, - eq, - inArray, - isNull, - sql, - syncPageComponentToMonitorsToPageInsertMany, -} from "@openstatus/db"; +import { type SQL, and, desc, eq, inArray, isNull, sql } from "@openstatus/db"; import { insertPageSchema, monitor, @@ -155,19 +146,6 @@ export const pageRouter = createTRPCRouter({ .insert(pageComponent) .values(pageComponentValues) .run(); - - // Build values for reverse sync to monitorsToPages - const monitorsToPageValues = monitors.map(({ monitorId }, index) => ({ - pageId: newPage.id, - order: index, - monitorId, - })); - - // Reverse sync to monitorsToPages (for backwards compatibility) - await syncPageComponentToMonitorsToPageInsertMany( - opts.ctx.db, - monitorsToPageValues, - ); } return newPage; diff --git a/packages/api/src/router/statusPage.utils.ts b/packages/api/src/router/statusPage.utils.ts index bc383a0ee..ba7bf2d19 100644 --- a/packages/api/src/router/statusPage.utils.ts +++ b/packages/api/src/router/statusPage.utils.ts @@ -34,102 +34,6 @@ export function isMonitorComponent( ); } -/** - * Transforms pageComponents to legacy monitorsToStatusReports format - */ -export function transformToMonitorsToStatusReports( - statusReportId: number, - pageComponents: PageComponentWithMonitorRelation[], -) { - const monitors = pageComponents.filter(isMonitorComponent); - return monitors.map((m) => ({ - statusReportId, - monitorId: m.monitor.id, - monitor: m.monitor, - })); -} - -/** - * Transforms pageComponents to legacy maintenancesToMonitors format - */ -export function transformToMaintenancesToMonitors( - maintenanceId: number, - pageComponents: PageComponentWithMonitorRelation[], -) { - const monitors = pageComponents.filter(isMonitorComponent); - return monitors.map((m) => ({ - maintenanceId, - monitorId: m.monitor.id, - monitor: m.monitor, - })); -} - -/** - * Transforms statusReportsToPageComponents relations using a monitorByIdMap for performance - */ -export function transformStatusReportWithPageComponents< - T extends { - id: number; - statusReportsToPageComponents: Array<{ - pageComponent: PageComponent | null; - }>; - }, ->( - report: T, - monitorByIdMap: Map< - number, - NonNullable - >, -) { - return { - ...report, - monitorsToStatusReports: report.statusReportsToPageComponents.flatMap( - (r) => { - const pc = r.pageComponent; - if (!pc?.monitorId) return []; - const monitor = monitorByIdMap.get(pc.monitorId); - if (!monitor) return []; - return [ - { statusReportId: report.id, monitorId: pc.monitorId, monitor }, - ]; - }, - ), - }; -} - -/** - * Transforms maintenancesToPageComponents relations using a monitorByIdMap for performance - */ -export function transformMaintenanceWithPageComponents< - T extends { - id: number; - maintenancesToPageComponents: Array<{ - pageComponent: PageComponent | null; - }>; - }, ->( - maintenance: T, - monitorByIdMap: Map< - number, - NonNullable - >, -) { - return { - ...maintenance, - maintenancesToMonitors: maintenance.maintenancesToPageComponents.flatMap( - (mp) => { - const pc = mp.pageComponent; - if (!pc?.monitorId) return []; - const monitor = monitorByIdMap.get(pc.monitorId); - if (!monitor) return []; - return [ - { maintenanceId: maintenance.id, monitorId: pc.monitorId, monitor }, - ]; - }, - ), - }; -} - export type StatusData = { day: string; count: number; diff --git a/packages/api/src/router/statusReport.ts b/packages/api/src/router/statusReport.ts index 4eeadfe48..b45a32a1a 100644 --- a/packages/api/src/router/statusReport.ts +++ b/packages/api/src/router/statusReport.ts @@ -1,15 +1,6 @@ import { z } from "zod"; -import { - type SQL, - and, - asc, - desc, - eq, - gte, - syncStatusReportToPageComponentDeleteByStatusReport, - syncStatusReportToPageComponentInsertMany, -} from "@openstatus/db"; +import { type SQL, and, asc, desc, eq, gte } from "@openstatus/db"; import { insertStatusReportUpdateSchema, selectPageComponentSchema, @@ -187,12 +178,6 @@ export const statusReportRouter = createTRPCRouter({ })), ) .run(); - // Reverse sync: page components -> monitors (for backward compatibility) - await syncStatusReportToPageComponentInsertMany( - tx, - newStatusReport.id, - opts.input.pageComponents, - ); } return { @@ -235,11 +220,6 @@ export const statusReportRouter = createTRPCRouter({ eq(statusReportsToPageComponents.statusReportId, opts.input.id), ) .run(); - // Reverse sync: delete from monitors (for backward compatibility) - await syncStatusReportToPageComponentDeleteByStatusReport( - tx, - opts.input.id, - ); if (opts.input.pageComponents.length > 0) { await tx @@ -251,12 +231,6 @@ export const statusReportRouter = createTRPCRouter({ })), ) .run(); - // Reverse sync: page components -> monitors (for backward compatibility) - await syncStatusReportToPageComponentInsertMany( - tx, - opts.input.id, - opts.input.pageComponents, - ); } }); }), diff --git a/packages/api/src/router/sync.test.ts b/packages/api/src/router/sync.test.ts deleted file mode 100644 index c1594c72d..000000000 --- a/packages/api/src/router/sync.test.ts +++ /dev/null @@ -1,506 +0,0 @@ -import { afterAll, beforeAll, describe, expect, test } from "bun:test"; -import { and, db, eq, inArray } from "@openstatus/db"; -import { - maintenance, - maintenancesToMonitors, - maintenancesToPageComponents, - monitor, - monitorGroup, - monitorsToPages, - monitorsToStatusReport, - page, - pageComponent, - pageComponentGroup, - statusReport, - statusReportUpdate, - statusReportsToPageComponents, -} from "@openstatus/db/src/schema"; -import { flyRegions } from "@openstatus/db/src/schema/constants"; -import { syncMonitorsToPageInsert } from "@openstatus/db/src/sync"; - -import { appRouter } from "../root"; -import { createInnerTRPCContext } from "../trpc"; - -/** - * Sync Tests: Verify that mutations to legacy tables also sync to new page_component tables - * - * Table mappings: - * - monitor_group -> page_component_groups - * - monitors_to_pages -> page_component - * - status_report_to_monitors -> status_report_to_page_component - * - maintenance_to_monitor -> maintenance_to_page_component - */ - -function getTestContext(limits?: unknown) { - return createInnerTRPCContext({ - req: undefined, - session: { - user: { - id: "1", - }, - }, - workspace: { - id: 1, - // @ts-expect-error - test context with partial limits - limits: limits || { - monitors: 100, - periodicity: ["30s", "1m", "5m", "10m", "30m", "1h"], - regions: flyRegions, - "status-pages": 10, - maintenance: true, - notifications: 10, - "status-subscribers": true, - sms: false, - pagerduty: true, - "password-protection": true, - "email-domain-protection": true, - "custom-domain": true, - }, - }, - }); -} - -// Test data identifiers -const TEST_PREFIX = "sync-test"; -let testPageId: number; -let testMonitorId: number; -let testPageComponentId: number; - -const monitorData = { - name: `${TEST_PREFIX}-monitor`, - url: "https://sync-test.example.com", - jobType: "http" as const, - method: "GET" as const, - periodicity: "1m" as const, - regions: [flyRegions[0]], - statusAssertions: [], - headerAssertions: [], - textBodyAssertions: [], - notifications: [], - pages: [] as number[], - tags: [], -}; - -beforeAll(async () => { - // Clean up any existing test data - await db - .delete(pageComponent) - .where(eq(pageComponent.name, `${TEST_PREFIX}-monitor`)); - await db.delete(page).where(eq(page.slug, `${TEST_PREFIX}-page`)); - await db.delete(monitor).where(eq(monitor.name, `${TEST_PREFIX}-monitor`)); - await db - .delete(monitor) - .where(eq(monitor.name, `${TEST_PREFIX}-deletable-monitor`)); - - // Create test page first - const testPage = await db - .insert(page) - .values({ - workspaceId: 1, - title: "Sync Test Page", - description: "A test page for sync tests", - slug: `${TEST_PREFIX}-page`, - customDomain: "", - }) - .returning() - .get(); - testPageId = testPage.id; - - // Create test monitor using tRPC (must be active for sync to work) - const ctx = getTestContext(); - const caller = appRouter.createCaller(ctx); - const createdMonitor = await caller.monitor.new({ - name: monitorData.name, - url: monitorData.url, - jobType: monitorData.jobType, - method: monitorData.method, - headers: [], - assertions: [], - active: true, // Changed to true - sync functions only sync active monitors - skipCheck: true, - }); - testMonitorId = createdMonitor.id; - - const createdPageComponent = await db - .insert(pageComponent) - .values({ - workspaceId: 1, - pageId: testPageId, - monitorId: testMonitorId, - type: "monitor", - name: `${TEST_PREFIX}-monitor`, - }) - .returning() - .get(); - testPageComponentId = createdPageComponent.id; -}); - -afterAll(async () => { - // Clean up test data in correct order (dependencies first) - await db - .delete(maintenancesToPageComponents) - .where( - inArray( - maintenancesToPageComponents.pageComponentId, - db - .select({ id: pageComponent.id }) - .from(pageComponent) - .where(eq(pageComponent.pageId, testPageId)), - ), - ); - await db - .delete(statusReportsToPageComponents) - .where( - inArray( - statusReportsToPageComponents.pageComponentId, - db - .select({ id: pageComponent.id }) - .from(pageComponent) - .where(eq(pageComponent.pageId, testPageId)), - ), - ); - await db.delete(pageComponent).where(eq(pageComponent.pageId, testPageId)); - await db - .delete(pageComponentGroup) - .where(eq(pageComponentGroup.pageId, testPageId)); - await db.delete(monitorGroup).where(eq(monitorGroup.pageId, testPageId)); - await db - .delete(monitorsToPages) - .where(eq(monitorsToPages.pageId, testPageId)); - await db - .delete(maintenancesToMonitors) - .where(eq(maintenancesToMonitors.monitorId, testMonitorId)); - await db - .delete(monitorsToStatusReport) - .where(eq(monitorsToStatusReport.monitorId, testMonitorId)); - await db - .delete(statusReportUpdate) - .where( - inArray( - statusReportUpdate.statusReportId, - db - .select({ id: statusReport.id }) - .from(statusReport) - .where(eq(statusReport.pageId, testPageId)), - ), - ); - await db.delete(statusReport).where(eq(statusReport.pageId, testPageId)); - await db.delete(maintenance).where(eq(maintenance.pageId, testPageId)); - await db.delete(page).where(eq(page.slug, `${TEST_PREFIX}-page`)); - await db.delete(monitor).where(eq(monitor.name, `${TEST_PREFIX}-monitor`)); - await db - .delete(monitor) - .where(eq(monitor.name, `${TEST_PREFIX}-deletable-monitor`)); -}); - -describe("Sync: monitors_to_pages -> page_component", () => {}); - -describe("Sync: maintenance_to_monitor -> maintenance_to_page_component", () => { - let testMaintenanceId: number; - - beforeAll(async () => { - // Ensure monitor is on the page first - use manual db call - await db - .insert(monitorsToPages) - .values({ - monitorId: testMonitorId, - pageId: testPageId, - order: 0, - }) - .onConflictDoNothing(); - - // Sync to page_component - await syncMonitorsToPageInsert(db, { - monitorId: testMonitorId, - pageId: testPageId, - order: 0, - }); - }); - - afterAll(async () => { - if (testMaintenanceId) { - await db - .delete(maintenancesToPageComponents) - .where( - eq(maintenancesToPageComponents.maintenanceId, testMaintenanceId), - ); - await db - .delete(maintenancesToMonitors) - .where(eq(maintenancesToMonitors.maintenanceId, testMaintenanceId)); - await db.delete(maintenance).where(eq(maintenance.id, testMaintenanceId)); - } - }); - - test("Creating maintenance with monitors syncs to maintenance_to_page_component", async () => { - const ctx = getTestContext(); - const caller = appRouter.createCaller(ctx); - - const from = new Date(); - const to = new Date(from.getTime() + 1000 * 60 * 60); - - const createdMaintenance = await caller.maintenance.new({ - title: `${TEST_PREFIX} Maintenance`, - message: "Test maintenance for sync", - startDate: from, - endDate: to, - pageId: testPageId, - pageComponents: [testPageComponentId], - }); - testMaintenanceId = createdMaintenance.id; - - // Verify maintenance_to_monitor was created - const maintenanceToMonitor = - await db.query.maintenancesToMonitors.findFirst({ - where: and( - eq(maintenancesToMonitors.maintenanceId, testMaintenanceId), - eq(maintenancesToMonitors.monitorId, testMonitorId), - ), - }); - expect(maintenanceToMonitor).toBeDefined(); - - // Verify maintenance_to_page_component was synced - const component = await db.query.pageComponent.findFirst({ - where: and( - eq(pageComponent.monitorId, testMonitorId), - eq(pageComponent.pageId, testPageId), - ), - }); - - if (component) { - const maintenanceToComponent = - await db.query.maintenancesToPageComponents.findFirst({ - where: and( - eq(maintenancesToPageComponents.maintenanceId, testMaintenanceId), - eq(maintenancesToPageComponents.pageComponentId, component.id), - ), - }); - expect(maintenanceToComponent).toBeDefined(); - } - }); - - test("Updating maintenance monitors syncs to maintenance_to_page_component", async () => { - const ctx = getTestContext(); - const caller = appRouter.createCaller(ctx); - - // Skip if no maintenance was created - if (!testMaintenanceId) return; - - const from = new Date(); - const to = new Date(from.getTime() + 1000 * 60 * 60); - - // Update maintenance to remove monitors - await caller.maintenance.update({ - id: testMaintenanceId, - title: `${TEST_PREFIX} Maintenance`, - message: "Updated maintenance", - startDate: from, - endDate: to, - pageComponents: [], - }); - - // Verify maintenance_to_monitor was deleted - const maintenanceToMonitor = - await db.query.maintenancesToMonitors.findFirst({ - where: and( - eq(maintenancesToMonitors.maintenanceId, testMaintenanceId), - eq(maintenancesToMonitors.monitorId, testMonitorId), - ), - }); - expect(maintenanceToMonitor).toBeUndefined(); - - // Verify maintenance_to_page_component was also deleted - const maintenanceToComponent = - await db.query.maintenancesToPageComponents.findFirst({ - where: eq( - maintenancesToPageComponents.maintenanceId, - testMaintenanceId, - ), - }); - expect(maintenanceToComponent).toBeUndefined(); - }); -}); - -describe("Sync: status_report_to_monitors -> status_report_to_page_component", () => { - let testStatusReportId: number; - - beforeAll(async () => { - const ctx = getTestContext(); - const _caller = appRouter.createCaller(ctx); - - // Ensure monitor is on the page first - await db - .insert(monitorsToPages) - .values({ - monitorId: testMonitorId, - pageId: testPageId, - order: 0, - }) - .onConflictDoNothing(); - - // Sync to page_component - await syncMonitorsToPageInsert(db, { - monitorId: testMonitorId, - pageId: testPageId, - order: 0, - }); - }); - - afterAll(async () => { - if (testStatusReportId) { - await db - .delete(statusReportsToPageComponents) - .where( - eq(statusReportsToPageComponents.statusReportId, testStatusReportId), - ); - await db - .delete(monitorsToStatusReport) - .where(eq(monitorsToStatusReport.statusReportId, testStatusReportId)); - await db - .delete(statusReportUpdate) - .where(eq(statusReportUpdate.statusReportId, testStatusReportId)); - await db - .delete(statusReport) - .where(eq(statusReport.id, testStatusReportId)); - } - }); - - test("Creating status report with monitors syncs to status_report_to_page_component", async () => { - const ctx = getTestContext(); - const caller = appRouter.createCaller(ctx); - - const createdReport = await caller.statusReport.create({ - title: `${TEST_PREFIX} Status Report`, - status: "investigating", - message: "Test status report for sync", - pageId: testPageId, - pageComponents: [testPageComponentId], - date: new Date(), - }); - testStatusReportId = createdReport.statusReportId; - - // Verify status_report_to_monitors was created - const reportToMonitor = await db.query.monitorsToStatusReport.findFirst({ - where: and( - eq(monitorsToStatusReport.statusReportId, testStatusReportId), - eq(monitorsToStatusReport.monitorId, testMonitorId), - ), - }); - expect(reportToMonitor).toBeDefined(); - - // Verify status_report_to_page_component was synced - const component = await db.query.pageComponent.findFirst({ - where: and( - eq(pageComponent.monitorId, testMonitorId), - eq(pageComponent.pageId, testPageId), - ), - }); - - if (component) { - const reportToComponent = - await db.query.statusReportsToPageComponents.findFirst({ - where: and( - eq( - statusReportsToPageComponents.statusReportId, - testStatusReportId, - ), - eq(statusReportsToPageComponents.pageComponentId, component.id), - ), - }); - expect(reportToComponent).toBeDefined(); - } - }); - - test("Updating status report monitors syncs to status_report_to_page_component", async () => { - const ctx = getTestContext(); - const caller = appRouter.createCaller(ctx); - - // Skip if no status report was created - if (!testStatusReportId) return; - - // Update status to remove monitors (using updateStatus procedure) - await caller.statusReport.updateStatus({ - id: testStatusReportId, - status: "resolved", - pageComponents: [], - title: `${TEST_PREFIX} Status Report`, - }); - - // Verify status_report_to_monitors was deleted - const reportToMonitor = await db.query.monitorsToStatusReport.findFirst({ - where: and( - eq(monitorsToStatusReport.statusReportId, testStatusReportId), - eq(monitorsToStatusReport.monitorId, testMonitorId), - ), - }); - expect(reportToMonitor).toBeUndefined(); - - // Verify status_report_to_page_component was also deleted - const reportToComponent = - await db.query.statusReportsToPageComponents.findFirst({ - where: eq( - statusReportsToPageComponents.statusReportId, - testStatusReportId, - ), - }); - expect(reportToComponent).toBeUndefined(); - }); -}); - -describe("Sync: monitor deletion cascades to page_component tables", () => { - let deletableMonitorId: number; - - beforeAll(async () => { - const ctx = getTestContext(); - const caller = appRouter.createCaller(ctx); - - // Create a monitor specifically for deletion tests (must be active for sync) - const deletableMonitor = await caller.monitor.new({ - name: `${TEST_PREFIX}-deletable-monitor`, - url: "https://delete-test.example.com", - jobType: "http" as const, - method: "GET" as const, - headers: [], - assertions: [], - active: true, // Changed to true - sync functions only sync active monitors - skipCheck: true, - }); - deletableMonitorId = deletableMonitor.id; - - // Add monitor to page - await db - .insert(monitorsToPages) - .values({ - monitorId: deletableMonitorId, - pageId: testPageId, - order: 0, - }) - .onConflictDoNothing(); - - // Sync to page_component - await syncMonitorsToPageInsert(db, { - monitorId: deletableMonitorId, - pageId: testPageId, - order: 0, - }); - }); - - test("Deleting monitor removes related page_component entries", async () => { - const ctx = getTestContext(); - const caller = appRouter.createCaller(ctx); - - // Verify page_component exists before deletion - let component = await db.query.pageComponent.findFirst({ - where: eq(pageComponent.monitorId, deletableMonitorId), - }); - expect(component).toBeDefined(); - - // Delete the monitor - await caller.monitor.delete({ id: deletableMonitorId }); - - // Verify page_component was removed - component = await db.query.pageComponent.findFirst({ - where: eq(pageComponent.monitorId, deletableMonitorId), - }); - expect(component).toBeUndefined(); - }); -}); diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts index 846029ee4..2e05a8b86 100644 --- a/packages/db/src/index.ts +++ b/packages/db/src/index.ts @@ -1,6 +1,5 @@ export * as schema from "./schema"; export * from "drizzle-orm"; export * from "./db"; -export * from "./sync"; // doing this because the external module not working see : https://github.com/vercel/next.js/issues/43433 // export * from "./sync-db"; diff --git a/packages/db/src/schema/maintenances/maintenance.ts b/packages/db/src/schema/maintenances/maintenance.ts index 423a77ebd..d89a48caf 100644 --- a/packages/db/src/schema/maintenances/maintenance.ts +++ b/packages/db/src/schema/maintenances/maintenance.ts @@ -30,6 +30,11 @@ export const maintenance = sqliteTable("maintenance", { ), }); +/** + * @deprecated Legacy relation table - code removed, table exists but completely unused + * All code now uses maintenancesToPageComponents table instead + * Table will be dropped in future migration after verification period + */ export const maintenancesToMonitors = sqliteTable( "maintenance_to_monitor", { @@ -64,9 +69,9 @@ export const maintenancesToMonitorsRelations = relations( ); export const maintenanceRelations = relations(maintenance, ({ one, many }) => ({ - // Legacy relation - will be deprecated after migration is complete + // Legacy relation - code removed, kept for type compatibility only maintenancesToMonitors: many(maintenancesToMonitors), - // New relation using pageComponents architecture + // Primary relation using pageComponents architecture maintenancesToPageComponents: many(maintenancesToPageComponents), page: one(page, { fields: [maintenance.pageId], diff --git a/packages/db/src/schema/monitors/monitor.ts b/packages/db/src/schema/monitors/monitor.ts index 27831dac4..8e91a4b1f 100644 --- a/packages/db/src/schema/monitors/monitor.ts +++ b/packages/db/src/schema/monitors/monitor.ts @@ -88,6 +88,11 @@ export const monitorRelation = relations(monitor, ({ one, many }) => ({ privateLocationToMonitors: many(privateLocationToMonitors), })); +/** + * @deprecated Legacy relation table - code removed, table exists but completely unused + * All code now uses pageComponent table instead + * Table will be dropped in future migration after verification period + */ export const monitorsToPages = sqliteTable( "monitors_to_pages", { diff --git a/packages/db/src/schema/status_reports/status_reports.ts b/packages/db/src/schema/status_reports/status_reports.ts index 24cc672a9..ae1b19d26 100644 --- a/packages/db/src/schema/status_reports/status_reports.ts +++ b/packages/db/src/schema/status_reports/status_reports.ts @@ -56,9 +56,9 @@ export const statusReportUpdate = sqliteTable("status_report_update", { export const StatusReportRelations = relations( statusReport, ({ one, many }) => ({ - // Legacy relation - will be deprecated after migration is complete + // Legacy relation - code removed, kept for type compatibility only monitorsToStatusReports: many(monitorsToStatusReport), - // New relation using pageComponents architecture + // Primary relation using pageComponents architecture statusReportsToPageComponents: many(statusReportsToPageComponents), page: one(page, { fields: [statusReport.pageId], @@ -82,6 +82,11 @@ export const statusReportUpdateRelations = relations( }), ); +/** + * @deprecated Legacy relation table - code removed, table exists but completely unused + * All code now uses statusReportsToPageComponents table instead + * Table will be dropped in future migration after verification period + */ export const monitorsToStatusReport = sqliteTable( "status_report_to_monitors", { diff --git a/packages/db/src/seed.mts b/packages/db/src/seed.mts index 33a5da262..ad57c5c6a 100644 --- a/packages/db/src/seed.mts +++ b/packages/db/src/seed.mts @@ -5,11 +5,8 @@ import { env } from "../env.mjs"; import { incidentTable, maintenance, - maintenancesToMonitors, maintenancesToPageComponents, monitor, - monitorsToPages, - monitorsToStatusReport, notification, notificationsToMonitors, page, @@ -166,12 +163,6 @@ async function main() { .onConflictDoNothing() .run(); - await db - .insert(monitorsToPages) - .values({ monitorId: 1, pageId: 1 }) - .onConflictDoNothing() - .run(); - // Page Components - representing monitors on the status page await db .insert(pageComponent) @@ -369,28 +360,6 @@ async function main() { .onConflictDoNothing() .run(); - await db - .insert(maintenancesToMonitors) - .values([ - { - maintenanceId: 1, - monitorId: 1, - }, - ]) - .onConflictDoNothing() - .run(); - - await db - .insert(monitorsToStatusReport) - .values([ - { - monitorId: 1, - statusReportId: 2, - }, - ]) - .onConflictDoNothing() - .run(); - // Link status reports to page components await db .insert(statusReportsToPageComponents) diff --git a/packages/db/src/sync.ts b/packages/db/src/sync.ts deleted file mode 100644 index 8ff232ecb..000000000 --- a/packages/db/src/sync.ts +++ /dev/null @@ -1,760 +0,0 @@ -import { and, eq, inArray, sql } from "drizzle-orm"; - -import type { db } from "./db"; -import { - maintenance, - maintenancesToMonitors, - maintenancesToPageComponents, - monitor, - monitorsToPages, - monitorsToStatusReport, - pageComponent, - pageComponentGroup, - statusReport, - statusReportsToPageComponents, -} from "./schema"; - -// Type that works with both LibSQLDatabase and SQLiteTransaction -// Using any to allow both db instance and transaction to be passed -// biome-ignore lint/suspicious/noExplicitAny: Compatible with both db and transaction -type DB = typeof db; -// Extract transaction type from the callback parameter of db.transaction() -type Transaction = Parameters[0]>[0]; - -// ============================================================================ -// Monitor Group <-> Page Component Group Sync -// ============================================================================ - -/** - * Syncs a monitor group insert to page_component_groups - */ -export async function syncMonitorGroupInsert( - db: DB | Transaction, - data: { - id: number; - workspaceId: number; - pageId: number; - name: string; - }, -) { - await db - .insert(pageComponentGroup) - .values({ - id: data.id, - workspaceId: data.workspaceId, - pageId: data.pageId, - name: data.name, - }) - .onConflictDoNothing(); -} - -/** - * Syncs a monitor group delete to page_component_groups - */ -export async function syncMonitorGroupDelete( - db: DB | Transaction, - monitorGroupId: number, -) { - await db - .delete(pageComponentGroup) - .where(eq(pageComponentGroup.id, monitorGroupId)); -} - -/** - * Syncs multiple monitor group deletes to page_component_groups - */ -export async function syncMonitorGroupDeleteMany( - db: DB | Transaction, - monitorGroupIds: number[], -) { - if (monitorGroupIds.length === 0) return; - await db - .delete(pageComponentGroup) - .where(inArray(pageComponentGroup.id, monitorGroupIds)); -} - -// ============================================================================ -// Monitors to Pages <-> Page Component Sync -// ============================================================================ - -/** - * Syncs a monitors_to_pages insert to page_component - * Requires monitor data to get name and workspace_id - */ -export async function syncMonitorsToPageInsert( - db: DB | Transaction, - data: { - monitorId: number; - pageId: number; - order?: number; - monitorGroupId?: number | null; - groupOrder?: number; - }, -) { - // Get monitor data for name and workspace_id (only active monitors) - const monitorData = await db - .select({ - id: monitor.id, - name: monitor.name, - externalName: monitor.externalName, - workspaceId: monitor.workspaceId, - active: monitor.active, - }) - .from(monitor) - .where(eq(monitor.id, data.monitorId)) - .get(); - - // Skip if monitor doesn't exist, has no workspace, or is inactive - if (!monitorData || !monitorData.workspaceId || !monitorData.active) return; - - await db - .insert(pageComponent) - .values({ - workspaceId: monitorData.workspaceId, - pageId: data.pageId, - type: "monitor", - monitorId: data.monitorId, - name: monitorData.externalName || monitorData.name, - order: data.order ?? 0, - groupId: data.monitorGroupId ?? null, - groupOrder: data.groupOrder ?? 0, - }) - .onConflictDoNothing(); -} - -/** - * Syncs multiple monitors_to_pages inserts to page_component - */ -export async function syncMonitorsToPageInsertMany( - db: DB | Transaction, - items: Array<{ - monitorId: number; - pageId: number; - order?: number; - monitorGroupId?: number | null; - groupOrder?: number; - }>, -) { - if (items.length === 0) return; - - // Get all monitor data in one query (only active monitors) - const monitorIds = [...new Set(items.map((item) => item.monitorId))]; - const monitors = await db - .select({ - id: monitor.id, - name: monitor.name, - externalName: monitor.externalName, - workspaceId: monitor.workspaceId, - active: monitor.active, - }) - .from(monitor) - .where(and(inArray(monitor.id, monitorIds), eq(monitor.active, true))); - - const monitorMap = new Map(monitors.map((m) => [m.id, m])); - - const values = items - .map((item) => { - const m = monitorMap.get(item.monitorId); - // Skip if monitor doesn't exist, has no workspace, or is inactive - if (!m || !m.workspaceId || !m.active) return null; - return { - workspaceId: m.workspaceId, - pageId: item.pageId, - type: "monitor" as const, - monitorId: item.monitorId, - name: m.externalName || m.name, - order: item.order ?? 0, - groupId: item.monitorGroupId ?? null, - groupOrder: item.groupOrder ?? 0, - }; - }) - .filter((v): v is NonNullable => v !== null); - - if (values.length === 0) return; - - await db.insert(pageComponent).values(values).onConflictDoNothing(); -} - -/** - * Syncs multiple monitors_to_pages upserts to page_component - * Updates order, groupId, groupOrder for existing components, inserts new ones - */ -export async function syncMonitorsToPageUpsertMany( - db: DB | Transaction, - items: Array<{ - monitorId: number; - pageId: number; - order?: number; - monitorGroupId?: number | null; - groupOrder?: number; - }>, -) { - if (items.length === 0) return; - - // Get all monitor data in one query (only active monitors) - const monitorIds = [...new Set(items.map((item) => item.monitorId))]; - const monitors = await db - .select({ - id: monitor.id, - name: monitor.name, - externalName: monitor.externalName, - workspaceId: monitor.workspaceId, - active: monitor.active, - }) - .from(monitor) - .where(and(inArray(monitor.id, monitorIds), eq(monitor.active, true))); - - const monitorMap = new Map(monitors.map((m) => [m.id, m])); - - const values = items - .map((item) => { - const m = monitorMap.get(item.monitorId); - // Skip if monitor doesn't exist, has no workspace, or is inactive - if (!m || !m.workspaceId || !m.active) return null; - return { - workspaceId: m.workspaceId, - pageId: item.pageId, - type: "monitor" as const, - monitorId: item.monitorId, - name: m.externalName || m.name, - order: item.order ?? 0, - groupId: item.monitorGroupId ?? null, - groupOrder: item.groupOrder ?? 0, - }; - }) - .filter((v): v is NonNullable => v !== null); - - if (values.length === 0) return; - - // Use onConflictDoUpdate to update existing page components - // The unique constraint is on (pageId, monitorId) - await db - .insert(pageComponent) - .values(values) - .onConflictDoUpdate({ - target: [pageComponent.pageId, pageComponent.monitorId], - set: { - order: sql.raw("excluded.`order`"), - groupId: sql.raw("excluded.`group_id`"), - groupOrder: sql.raw("excluded.`group_order`"), - }, - }); -} - -/** - * Syncs a monitors_to_pages delete to page_component - */ -export async function syncMonitorsToPageDelete( - db: DB | Transaction, - data: { monitorId: number; pageId: number }, -) { - await db - .delete(pageComponent) - .where( - and( - eq(pageComponent.monitorId, data.monitorId), - eq(pageComponent.pageId, data.pageId), - ), - ); -} - -/** - * REVERSE SYNC: Syncs a page_component delete to monitors_to_pages - * Used when pageComponent is the primary table and monitorsToPages is kept for backwards compatibility - */ -export async function syncPageComponentToMonitorsToPageDelete( - db: DB | Transaction, - data: { monitorId: number; pageId: number }, -) { - await db - .delete(monitorsToPages) - .where( - and( - eq(monitorsToPages.monitorId, data.monitorId), - eq(monitorsToPages.pageId, data.pageId), - ), - ); -} - -/** - * Syncs monitors_to_pages deletes for a specific page to page_component - */ -export async function syncMonitorsToPageDeleteByPage( - db: DB | Transaction, - pageId: number, -) { - await db - .delete(pageComponent) - .where( - and(eq(pageComponent.pageId, pageId), eq(pageComponent.type, "monitor")), - ); -} - -/** - * Syncs monitors_to_pages deletes for specific monitors to page_component - */ -export async function syncMonitorsToPageDeleteByMonitors( - db: DB | Transaction, - monitorIds: number[], -) { - if (monitorIds.length === 0) return; - await db - .delete(pageComponent) - .where(inArray(pageComponent.monitorId, monitorIds)); -} - -/** - * REVERSE SYNC: Syncs page_component inserts to monitors_to_pages - * Used when pageComponents is the primary table and monitorsToPages is kept for backwards compatibility - */ -export async function syncPageComponentToMonitorsToPageInsertMany( - db: DB | Transaction, - items: Array<{ - monitorId: number; - pageId: number; - order?: number; - monitorGroupId?: number | null; - groupOrder?: number; - }>, -) { - if (items.length === 0) return; - - await db - .insert(monitorsToPages) - .values( - items.map((item) => ({ - monitorId: item.monitorId, - pageId: item.pageId, - order: item.order ?? 0, - monitorGroupId: item.monitorGroupId ?? null, - groupOrder: item.groupOrder ?? 0, - })), - ) - .onConflictDoNothing(); -} - -// ============================================================================ -// Status Report to Monitors <-> Status Report to Page Components Sync -// ============================================================================ - -/** - * Syncs a status_report_to_monitors insert to status_report_to_page_component - */ -export async function syncStatusReportToMonitorInsert( - db: DB | Transaction, - data: { statusReportId: number; monitorId: number }, -) { - // Get the status report's page_id - const report = await db - .select({ pageId: statusReport.pageId }) - .from(statusReport) - .where(eq(statusReport.id, data.statusReportId)) - .get(); - - // Find matching page_components - const components = await db - .select({ id: pageComponent.id }) - .from(pageComponent) - .where( - and( - eq(pageComponent.monitorId, data.monitorId), - report?.pageId - ? eq(pageComponent.pageId, report.pageId) - : // If no page_id on status report, match all page_components with this monitor - eq(pageComponent.monitorId, data.monitorId), - ), - ); - - if (components.length === 0) return; - - await db - .insert(statusReportsToPageComponents) - .values( - components.map((c) => ({ - statusReportId: data.statusReportId, - pageComponentId: c.id, - })), - ) - .onConflictDoNothing(); -} - -/** - * Syncs multiple status_report_to_monitors inserts to status_report_to_page_component - */ -export async function syncStatusReportToMonitorInsertMany( - db: DB | Transaction, - statusReportId: number, - monitorIds: number[], -) { - if (monitorIds.length === 0) return; - - // Get the status report's page_id - const report = await db - .select({ pageId: statusReport.pageId }) - .from(statusReport) - .where(eq(statusReport.id, statusReportId)) - .get(); - - // Find matching page_components for all monitors - const components = await db - .select({ id: pageComponent.id, monitorId: pageComponent.monitorId }) - .from(pageComponent) - .where( - and( - inArray(pageComponent.monitorId, monitorIds), - report?.pageId - ? eq(pageComponent.pageId, report.pageId) - : // If no page_id, we need to be careful - get components that match the monitor - inArray(pageComponent.monitorId, monitorIds), - ), - ); - - if (components.length === 0) return; - - await db - .insert(statusReportsToPageComponents) - .values( - components.map((c) => ({ - statusReportId, - pageComponentId: c.id, - })), - ) - .onConflictDoNothing(); -} - -/** - * Syncs a status_report_to_monitors delete to status_report_to_page_component - */ -export async function syncStatusReportToMonitorDelete( - db: DB | Transaction, - data: { statusReportId: number; monitorId: number }, -) { - // Find page_components with this monitor - const components = await db - .select({ id: pageComponent.id }) - .from(pageComponent) - .where(eq(pageComponent.monitorId, data.monitorId)); - - if (components.length === 0) return; - - await db.delete(statusReportsToPageComponents).where( - and( - eq(statusReportsToPageComponents.statusReportId, data.statusReportId), - inArray( - statusReportsToPageComponents.pageComponentId, - components.map((c) => c.id), - ), - ), - ); -} - -/** - * Syncs status_report_to_monitors deletes for a specific status report - */ -export async function syncStatusReportToMonitorDeleteByStatusReport( - db: DB | Transaction, - statusReportId: number, -) { - await db - .delete(statusReportsToPageComponents) - .where(eq(statusReportsToPageComponents.statusReportId, statusReportId)); -} - -/** - * Syncs status_report_to_monitors deletes for specific monitors - */ -export async function syncStatusReportToMonitorDeleteByMonitors( - db: DB | Transaction, - monitorIds: number[], -) { - if (monitorIds.length === 0) return; - - // Find page_components with these monitors - const components = await db - .select({ id: pageComponent.id }) - .from(pageComponent) - .where(inArray(pageComponent.monitorId, monitorIds)); - - if (components.length === 0) return; - - await db.delete(statusReportsToPageComponents).where( - inArray( - statusReportsToPageComponents.pageComponentId, - components.map((c) => c.id), - ), - ); -} - -/** - * Syncs status_report_to_page_component inserts to status_report_to_monitors - * This is the inverse of syncStatusReportToMonitorInsertMany - */ -export async function syncStatusReportToPageComponentInsertMany( - db: DB | Transaction, - statusReportId: number, - pageComponentIds: number[], -) { - if (pageComponentIds.length === 0) return; - - // Find monitor IDs from the page components - // Only get components that have a monitorId (not static components) - const components = await db - .select({ monitorId: pageComponent.monitorId }) - .from(pageComponent) - .where( - and( - inArray(pageComponent.id, pageComponentIds), - eq(pageComponent.type, "monitor"), - ), - ); - - if (components.length === 0) return; - - // Extract unique monitor IDs (filter out nulls) - const monitorIds = [ - ...new Set( - components - .map((c) => c.monitorId) - .filter((id): id is number => id !== null), - ), - ]; - - if (monitorIds.length === 0) return; - - // Insert into monitorsToStatusReport - await db - .insert(monitorsToStatusReport) - .values( - monitorIds.map((monitorId) => ({ - statusReportId, - monitorId, - })), - ) - .onConflictDoNothing(); -} - -/** - * Syncs status_report_to_page_component deletes to status_report_to_monitors - * This is the inverse of syncStatusReportToMonitorDeleteByStatusReport - * When page components are removed from a status report, remove the corresponding monitors - */ -export async function syncStatusReportToPageComponentDeleteByStatusReport( - db: DB | Transaction, - statusReportId: number, -) { - await db - .delete(monitorsToStatusReport) - .where(eq(monitorsToStatusReport.statusReportId, statusReportId)); -} - -// ============================================================================ -// Maintenance to Monitor <-> Maintenance to Page Component Sync -// ============================================================================ - -/** - * Syncs a maintenance_to_monitor insert to maintenance_to_page_component - */ -export async function syncMaintenanceToMonitorInsert( - db: DB | Transaction, - data: { maintenanceId: number; monitorId: number }, -) { - // Get the maintenance's page_id - const maint = await db - .select({ pageId: maintenance.pageId }) - .from(maintenance) - .where(eq(maintenance.id, data.maintenanceId)) - .get(); - - // Find matching page_components - const components = await db - .select({ id: pageComponent.id }) - .from(pageComponent) - .where( - and( - eq(pageComponent.monitorId, data.monitorId), - maint?.pageId - ? eq(pageComponent.pageId, maint.pageId) - : eq(pageComponent.monitorId, data.monitorId), - ), - ); - - if (components.length === 0) return; - - await db - .insert(maintenancesToPageComponents) - .values( - components.map((c) => ({ - maintenanceId: data.maintenanceId, - pageComponentId: c.id, - })), - ) - .onConflictDoNothing(); -} - -/** - * Syncs multiple maintenance_to_monitor inserts to maintenance_to_page_component - */ -export async function syncMaintenanceToMonitorInsertMany( - db: DB | Transaction, - maintenanceId: number, - monitorIds: number[], -) { - if (monitorIds.length === 0) return; - - // Get the maintenance's page_id - const maint = await db - .select({ pageId: maintenance.pageId }) - .from(maintenance) - .where(eq(maintenance.id, maintenanceId)) - .get(); - - // Find matching page_components for all monitors - const components = await db - .select({ id: pageComponent.id, monitorId: pageComponent.monitorId }) - .from(pageComponent) - .where( - and( - inArray(pageComponent.monitorId, monitorIds), - maint?.pageId - ? eq(pageComponent.pageId, maint.pageId) - : inArray(pageComponent.monitorId, monitorIds), - ), - ); - - if (components.length === 0) return; - - await db - .insert(maintenancesToPageComponents) - .values( - components.map((c) => ({ - maintenanceId, - pageComponentId: c.id, - })), - ) - .onConflictDoNothing(); -} - -/** - * Syncs a maintenance_to_monitor delete to maintenance_to_page_component - */ -export async function syncMaintenanceToMonitorDelete( - db: DB | Transaction, - data: { maintenanceId: number; monitorId: number }, -) { - // Find page_components with this monitor - const components = await db - .select({ id: pageComponent.id }) - .from(pageComponent) - .where(eq(pageComponent.monitorId, data.monitorId)); - - if (components.length === 0) return; - - await db.delete(maintenancesToPageComponents).where( - and( - eq(maintenancesToPageComponents.maintenanceId, data.maintenanceId), - inArray( - maintenancesToPageComponents.pageComponentId, - components.map((c) => c.id), - ), - ), - ); -} - -/** - * Syncs maintenance_to_monitor deletes for a specific maintenance - */ -export async function syncMaintenanceToMonitorDeleteByMaintenance( - db: DB | Transaction, - maintenanceId: number, -) { - await db - .delete(maintenancesToPageComponents) - .where(eq(maintenancesToPageComponents.maintenanceId, maintenanceId)); -} - -/** - * Syncs maintenance_to_monitor deletes for specific monitors - */ -export async function syncMaintenanceToMonitorDeleteByMonitors( - db: DB | Transaction, - monitorIds: number[], -) { - if (monitorIds.length === 0) return; - - // Find page_components with these monitors - const components = await db - .select({ id: pageComponent.id }) - .from(pageComponent) - .where(inArray(pageComponent.monitorId, monitorIds)); - - if (components.length === 0) return; - - await db.delete(maintenancesToPageComponents).where( - inArray( - maintenancesToPageComponents.pageComponentId, - components.map((c) => c.id), - ), - ); -} - -/** - * Syncs maintenance_to_page_component inserts to maintenance_to_monitors - * This is the inverse of syncMaintenanceToMonitorInsertMany - */ -export async function syncMaintenanceToPageComponentInsertMany( - db: DB | Transaction, - maintenanceId: number, - pageComponentIds: number[], -) { - if (pageComponentIds.length === 0) return; - - // Find monitor IDs from the page components - // Only get components that have a monitorId (not static components) - const components = await db - .select({ monitorId: pageComponent.monitorId }) - .from(pageComponent) - .where( - and( - inArray(pageComponent.id, pageComponentIds), - eq(pageComponent.type, "monitor"), - ), - ); - - if (components.length === 0) return; - - // Extract unique monitor IDs (filter out nulls) - const monitorIds = [ - ...new Set( - components - .map((c) => c.monitorId) - .filter((id): id is number => id !== null), - ), - ]; - - if (monitorIds.length === 0) return; - - // Insert into maintenancesToMonitors - await db - .insert(maintenancesToMonitors) - .values( - monitorIds.map((monitorId) => ({ - maintenanceId, - monitorId, - })), - ) - .onConflictDoNothing(); -} - -/** - * Syncs maintenance_to_page_component deletes to maintenance_to_monitors - * This is the inverse of syncMaintenanceToMonitorDeleteByMaintenance - * When page components are removed from a maintenance, remove the corresponding monitors - */ -export async function syncMaintenanceToPageComponentDeleteByMaintenance( - db: DB | Transaction, - maintenanceId: number, -) { - await db - .delete(maintenancesToMonitors) - .where(eq(maintenancesToMonitors.maintenanceId, maintenanceId)); -} -- 2.54.0