Openstatus www.openstatus.dev
at 4c0f4c00a38753a5d0dfd7e7b7b7706dec6f1503 515 lines 14 kB view raw
1import { and, eq, inArray } from "drizzle-orm"; 2 3import type { db } from "./db"; 4import { 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 17type DB = typeof db; 18// Extract transaction type from the callback parameter of db.transaction() 19type 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 */ 28export 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 */ 51export 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 */ 63export 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 */ 81export 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 */ 123export 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 */ 174export 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 */ 191export 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 */ 205export 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 */ 222export 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 */ 263export 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 */ 307export 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 */ 333export 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 */ 345export 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 */ 374export 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 */ 414export 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 */ 457export 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 */ 483export 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 */ 495export 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}