From d40b766c83e947915a2b515cda100aab71b1ef55 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 16 Apr 2026 11:12:06 -0700 Subject: [PATCH] timescaleDB implementation --- .../monitors/TimescaleMonitorsRepository.ts | 51 ++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/server/src/repositories/monitors/TimescaleMonitorsRepository.ts b/server/src/repositories/monitors/TimescaleMonitorsRepository.ts index a94d38504..7cd0aa4f0 100644 --- a/server/src/repositories/monitors/TimescaleMonitorsRepository.ts +++ b/server/src/repositories/monitors/TimescaleMonitorsRepository.ts @@ -1,5 +1,5 @@ import type { Pool } from "pg"; -import type { Monitor, MonitorsSummary, MonitorStatus, MonitorType, MonitorMatchMethod, GeoContinent } from "@/types/monitor.js"; +import type { Monitor, MonitorsSummary, MonitorStatus, MonitorType, MonitorMatchMethod, GeoContinent, CheckSnapshot } from "@/types/monitor.js"; import type { IMonitorsRepository, TeamQueryConfig, SummaryConfig } from "./IMonitorsRepository.js"; import { AppError } from "@/utils/AppError.js"; @@ -641,6 +641,55 @@ export class TimescaleMonitorsRepository implements IMonitorsRepository { return entity; }; + updateStatusWindowAndChecks = async ( + monitorId: string, + teamId: string, + status: boolean, + _checkSnapshot: CheckSnapshot, + windowSize: number, + _maxRecentChecks: number, + statusPatch?: Partial + ): Promise => { + // In TimescaleDB, recentChecks live in the checks table (inserted by the buffer service), + // so we only need to atomically update status_window and any statusPatch fields. + const sets: string[] = [ + `status_window = (array_append(COALESCE(status_window, ARRAY[]::boolean[]), $1))[array_length(array_append(COALESCE(status_window, ARRAY[]::boolean[]), $1), 1) - $2 + 1:]`, + `updated_at = NOW()`, + ]; + const values: unknown[] = [status, windowSize]; + let paramIndex = 3; + + if (statusPatch) { + const patchFieldMap: [keyof Monitor, string][] = [ + ["status", "status"], + ["cpuAlertCounter", "cpu_alert_counter"], + ["memoryAlertCounter", "memory_alert_counter"], + ["diskAlertCounter", "disk_alert_counter"], + ["tempAlertCounter", "temp_alert_counter"], + ]; + for (const [key, column] of patchFieldMap) { + if (statusPatch[key] !== undefined) { + sets.push(`${column} = $${paramIndex++}`); + values.push(statusPatch[key]); + } + } + } + + values.push(monitorId, teamId); + const result = await this.pool.query( + `UPDATE monitors SET ${sets.join(", ")} WHERE id = $${paramIndex++} AND team_id = $${paramIndex} + RETURNING ${MONITOR_COLUMNS}`, + values + ); + const row = result.rows[0]; + if (!row) { + throw new AppError({ message: `Failed to update status and checks for monitor with id ${monitorId}`, status: 500 }); + } + const entity = this.toEntity(row); + entity.notifications = await this.fetchNotificationIds([monitorId]).then((m) => m.get(monitorId) ?? []); + return entity; + }; + togglePauseById = async (monitorId: string, teamId: string): Promise => { const result = await this.pool.query( `UPDATE monitors SET