Openstatus
www.openstatus.dev
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}