backend implementation

This commit is contained in:
Alex Holliday committed 2026-03-09 11:14:40 -07:00
1 parent 0058772f61
commit d9cb81e71d
10 files changed
+117 -75

No files matched your search

+1
View File
@@ -252,6 +252,7 @@ export const initializeServices = async ({
statusService, statusService,
notificationsService, notificationsService,
checkService, checkService,
settingsService,
bufferService, bufferService,
incidentService, incidentService,
maintenanceWindowsRepository, maintenanceWindowsRepository,
+1 -11
View File
@@ -21,13 +21,11 @@ type CheckMetadataDocument = Omit<CheckMetadata, "monitorId" | "teamId"> & {
type: MonitorType; type: MonitorType;
}; };
type CheckDocumentBase = Omit<Check, "id" | "metadata" | "expiry" | "ackAt" | "createdAt" | "updatedAt"> & { type CheckDocumentBase = Omit<Check, "id" | "metadata" | "expiry" | "createdAt" | "updatedAt"> & {
metadata: CheckMetadataDocument; metadata: CheckMetadataDocument;
expiry: Date; expiry: Date;
ackAt?: Date | null;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
__v: number;
}; };
interface CheckDocument extends CheckDocumentBase { interface CheckDocument extends CheckDocumentBase {
@@ -229,14 +227,6 @@ const CheckSchema = new Schema<CheckDocument>(
type: Date, type: Date,
default: Date.now, default: Date.now,
}, },
ack: {
type: Boolean,
default: false,
},
ackAt: {
type: Date,
default: undefined,
},
cpu: { cpu: {
type: cpuSchema, type: cpuSchema,
default: () => ({}), default: () => ({}),
@@ -47,4 +47,5 @@ export interface IChecksRepository {
deleteByMonitorId(monitorId: string): Promise<number>; deleteByMonitorId(monitorId: string): Promise<number>;
deleteByTeamId(teamId: string): Promise<number>; deleteByTeamId(teamId: string): Promise<number>;
deleteByMonitorIdsNotIn(monitorIds: string[]): Promise<number>; deleteByMonitorIdsNotIn(monitorIds: string[]): Promise<number>;
deleteOlderThan(date: Date): Promise<number>;
} }
@@ -46,13 +46,6 @@ class MongoChecksRepository implements IChecksRepository {
return value instanceof Date ? value.toISOString() : new Date(value).toISOString(); return value instanceof Date ? value.toISOString() : new Date(value).toISOString();
}; };
const toOptionalDateString = (value?: Date | string | null): string | undefined => {
if (!value) {
return undefined;
}
return toDateString(value);
};
const mapTimings = (timings?: GotTimings): GotTimings => { const mapTimings = (timings?: GotTimings): GotTimings => {
const phases = timings?.phases ?? { const phases = timings?.phases ?? {
wait: 0, wait: 0,
@@ -171,8 +164,6 @@ class MongoChecksRepository implements IChecksRepository {
timings: mapTimings(doc.timings), timings: mapTimings(doc.timings),
statusCode: doc.statusCode ?? 0, statusCode: doc.statusCode ?? 0,
message: doc.message ?? "", message: doc.message ?? "",
ack: doc.ack ?? false,
ackAt: toOptionalDateString(doc.ackAt),
expiry: toDateString(doc.expiry), expiry: toDateString(doc.expiry),
cpu: mapCpu(doc.cpu), cpu: mapCpu(doc.cpu),
memory: mapMemory(doc.memory), memory: mapMemory(doc.memory),
@@ -186,7 +177,6 @@ class MongoChecksRepository implements IChecksRepository {
seo: doc.seo, seo: doc.seo,
performance: doc.performance, performance: doc.performance,
audits: mapAudits(doc.audits), audits: mapAudits(doc.audits),
__v: doc.__v ?? 0,
createdAt: toDateString(doc.createdAt), createdAt: toDateString(doc.createdAt),
updatedAt: toDateString(doc.updatedAt), updatedAt: toDateString(doc.updatedAt),
}; };
@@ -412,6 +402,11 @@ class MongoChecksRepository implements IChecksRepository {
return result.deletedCount ?? 0; return result.deletedCount ?? 0;
}; };
deleteOlderThan = async (date: Date): Promise<number> => {
const result = await CheckModel.deleteMany({ createdAt: { $lt: date } });
return result.deletedCount ?? 0;
};
private findUptimeDateRangeChecks = async ( private findUptimeDateRangeChecks = async (
monitorType: Exclude<MonitorType, "hardware" | "pagespeed">, monitorType: Exclude<MonitorType, "hardware" | "pagespeed">,
monitorObjectId: mongoose.Types.ObjectId, monitorObjectId: mongoose.Types.ObjectId,
+26 -5
View File
@@ -1,6 +1,14 @@
import { Types } from "mongoose"; import { Types } from "mongoose";
import { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js"; import { IChecksRepository, IMonitorsRepository } from "@/repositories/index.js";
import type { MonitorStatusResponse, CheckErrorInfo, Check, ILighthouseAudit, ChecksQueryResult, ChecksSummary } from "@/types/index.js"; import type {
MonitorStatusResponse,
CheckErrorInfo,
Check,
ILighthouseAudit,
ChecksQueryResult,
ChecksSummary,
MonitorPayloadMap,
} from "@/types/index.js";
import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js"; import type { HardwareStatusPayload, PageSpeedStatusPayload } from "@/types/network.js";
import { AppError } from "@/utils/AppError.js"; import { AppError } from "@/utils/AppError.js";
import { ParseBoolean } from "@/utils/utils.js"; import { ParseBoolean } from "@/utils/utils.js";
@@ -10,12 +18,13 @@ const SERVICE_NAME = "checkService";
export interface ICheckService { export interface ICheckService {
createChecks(checks: Check[]): Promise<Check[]>; createChecks(checks: Check[]): Promise<Check[]>;
buildCheck(statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>): Partial<Check> | undefined; buildCheck(statusResponse: MonitorStatusResponse<MonitorPayloadMap[keyof MonitorPayloadMap]>): Check | undefined;
getChecksByMonitor(params: { monitorId: string; query: Record<string, string>; teamId: string }): Promise<ChecksQueryResult>; getChecksByMonitor(params: { monitorId: string; query: Record<string, string>; teamId: string }): Promise<ChecksQueryResult>;
getChecksByTeam(params: { teamId: string; query: Record<string, string> }): Promise<ChecksQueryResult>; getChecksByTeam(params: { teamId: string; query: Record<string, string> }): Promise<ChecksQueryResult>;
getChecksSummaryByTeamId(params: { teamId: string; dateRange: string }): Promise<ChecksSummary>; getChecksSummaryByTeamId(params: { teamId: string; dateRange: string }): Promise<ChecksSummary>;
deleteChecks(params: { monitorId: string; teamId: string }): Promise<number>; deleteChecks(params: { monitorId: string; teamId: string }): Promise<number>;
deleteChecksByTeamId(params: { teamId: string }): Promise<number>; deleteChecksByTeamId(params: { teamId: string }): Promise<number>;
deleteOlderThan(date: Date): Promise<number>;
updateChecksTTL(params: { teamId: string; ttl: string }): Promise<void>; updateChecksTTL(params: { teamId: string; ttl: string }): Promise<void>;
} }
@@ -39,12 +48,14 @@ export class CheckService implements ICheckService {
return this.checksRepository.createChecks(checks); return this.checksRepository.createChecks(checks);
}; };
buildCheck = (statusResponse: MonitorStatusResponse<PageSpeedStatusPayload | HardwareStatusPayload | undefined>) => { buildCheck = (statusResponse: MonitorStatusResponse<MonitorPayloadMap[keyof MonitorPayloadMap]>): Check | undefined => {
const { monitorId, teamId, type, status, responseTime, code, message, payload, timings } = statusResponse; const { monitorId, teamId, type, status, responseTime, code, message, payload, timings } = statusResponse;
const check: Partial<Check> = { const now = new Date().toISOString();
const check: Check = {
id: new Types.ObjectId().toString(), id: new Types.ObjectId().toString(),
createdAt: new Date().toISOString(), createdAt: now,
updatedAt: now,
metadata: { metadata: {
monitorId, monitorId,
teamId, teamId,
@@ -55,6 +66,7 @@ export class CheckService implements ICheckService {
responseTime: responseTime || 0, responseTime: responseTime || 0,
timings: timings, timings: timings,
message, message,
expiry: now,
}; };
if (type === "pagespeed") { if (type === "pagespeed") {
@@ -190,6 +202,15 @@ export class CheckService implements ICheckService {
return deletedCount; return deletedCount;
}; };
deleteOlderThan = async (date: Date) => {
if (!date) {
throw new AppError({ message: "No date provided", service: SERVICE_NAME, method: "deleteChecksOlderThan", status: 400 });
}
const deletedCount = await this.checksRepository.deleteOlderThan(date);
return deletedCount;
};
updateChecksTTL = async ({ teamId, ttl }: { teamId: string; ttl: string }) => { updateChecksTTL = async ({ teamId, ttl }: { teamId: string; ttl: string }) => {
throw new AppError({ message: "Not implemented", service: SERVICE_NAME, method: "updateChecksTTL", status: 500, details: { teamId, ttl } }); throw new AppError({ message: "Not implemented", service: SERVICE_NAME, method: "updateChecksTTL", status: 500, details: { teamId, ttl } });
}; };
@@ -89,9 +89,10 @@ export class SuperSimpleQueue implements ISuperSimpleQueue {
try { try {
this.scheduler.start(); this.scheduler.start();
this.scheduler.addTemplate("monitor-job", this.helper.getMonitorJob()); this.scheduler.addTemplate("monitor-job", this.helper.getHeartbeatJob());
this.scheduler.addTemplate("geo-check-job", this.helper.getGeoCheckJob()); this.scheduler.addTemplate("geo-check-job", this.helper.getHeartbeatGeoJob());
this.scheduler.addTemplate("cleanup-orphaned", this.helper.getCleanupOrphanedJob()); this.scheduler.addTemplate("cleanup-orphaned", this.helper.getCleanupOrphanedJob());
this.scheduler.addTemplate("cleanup-retention-job", this.helper.getCleanupRetentionJob());
const monitors = await this.monitorsRepository.findAll(); const monitors = await this.monitorsRepository.findAll();
if (!monitors) { if (!monitors) {
return true; return true;
@@ -104,6 +105,7 @@ export class SuperSimpleQueue implements ISuperSimpleQueue {
} }
this.scheduler.addJob({ id: "cleanup-orphaned", template: "cleanup-orphaned", active: true }); this.scheduler.addJob({ id: "cleanup-orphaned", template: "cleanup-orphaned", active: true });
this.scheduler.addJob({ id: "cleanup-retention", template: "cleanup-retention-job", active: true, repeat: 24 * 60 * 60 * 1000 });
return true; return true;
} catch (error: any) { } catch (error: any) {
@@ -2,8 +2,16 @@ const SERVICE_NAME = "JobQueueHelper";
import type { Monitor } from "@/types/monitor.js"; import type { Monitor } from "@/types/monitor.js";
import { supportsGeoCheck } from "@/types/monitor.js"; import { supportsGeoCheck } from "@/types/monitor.js";
import { AppError } from "@/utils/AppError.js"; import { AppError } from "@/utils/AppError.js";
import { INetworkService, INotificationsService, IStatusService, IncidentService, type IGeoChecksService } from "@/service/index.js"; import {
import type { StatusChangeResult } from "@/types/index.js"; ICheckService,
INetworkService,
INotificationsService,
ISettingsService,
IStatusService,
IncidentService,
type IGeoChecksService,
} from "@/service/index.js";
import type { MaintenanceWindow, StatusChangeResult } from "@/types/index.js";
import { import {
IMaintenanceWindowsRepository, IMaintenanceWindowsRepository,
IMonitorsRepository, IMonitorsRepository,
@@ -18,9 +26,10 @@ import { IBufferService } from "@/service/index.js";
export interface ISuperSimpleQueueHelper { export interface ISuperSimpleQueueHelper {
readonly serviceName: string; readonly serviceName: string;
getMonitorJob(): (monitor: Monitor) => Promise<void>; getHeartbeatJob(): (monitor: Monitor) => Promise<void>;
getHeartbeatGeoJob(): (monitor: Monitor) => Promise<void>;
getCleanupOrphanedJob(): () => Promise<void>; getCleanupOrphanedJob(): () => Promise<void>;
getGeoCheckJob(): (monitor: Monitor) => Promise<void>; getCleanupRetentionJob(): () => Promise<void>;
isInMaintenanceWindow(monitorId: string, teamId: string): Promise<boolean>; isInMaintenanceWindow(monitorId: string, teamId: string): Promise<boolean>;
} }
@@ -41,11 +50,12 @@ export interface MonitorActionDecision {
export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper { export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
static SERVICE_NAME = SERVICE_NAME; static SERVICE_NAME = SERVICE_NAME;
private logger: any; private logger: ILogger;
private networkService: INetworkService; private networkService: INetworkService;
private statusService: IStatusService; private statusService: IStatusService;
private notificationsService: INotificationsService; private notificationsService: INotificationsService;
private checkService: any; private checkService: ICheckService;
private settingsService: ISettingsService;
private buffer: IBufferService; private buffer: IBufferService;
private incidentService: IncidentService; private incidentService: IncidentService;
private maintenanceWindowsRepository: IMaintenanceWindowsRepository; private maintenanceWindowsRepository: IMaintenanceWindowsRepository;
@@ -62,7 +72,8 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
networkService: INetworkService, networkService: INetworkService,
statusService: IStatusService, statusService: IStatusService,
notificationsService: INotificationsService, notificationsService: INotificationsService,
checkService: any, checkService: ICheckService,
settingsService: ISettingsService,
buffer: IBufferService, buffer: IBufferService,
incidentService: IncidentService, incidentService: IncidentService,
maintenanceWindowsRepository: IMaintenanceWindowsRepository, maintenanceWindowsRepository: IMaintenanceWindowsRepository,
@@ -78,6 +89,7 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
this.networkService = networkService; this.networkService = networkService;
this.statusService = statusService; this.statusService = statusService;
this.checkService = checkService; this.checkService = checkService;
this.settingsService = settingsService;
this.buffer = buffer; this.buffer = buffer;
this.notificationsService = notificationsService; this.notificationsService = notificationsService;
this.incidentService = incidentService; this.incidentService = incidentService;
@@ -95,7 +107,7 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
return SuperSimpleQueueHelper.SERVICE_NAME; return SuperSimpleQueueHelper.SERVICE_NAME;
} }
getMonitorJob = () => { getHeartbeatJob = () => {
return async (monitor: Monitor) => { return async (monitor: Monitor) => {
try { try {
const monitorId = monitor.id; const monitorId = monitor.id;
@@ -126,7 +138,7 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
} }
// Step 3. Build check // Step 3. Build check
const check = await this.checkService.buildCheck(status); const check = this.checkService.buildCheck(status);
if (!check) { if (!check) {
this.logger.warn({ this.logger.warn({
message: `No check could be built for monitor ${monitorId}`, message: `No check could be built for monitor ${monitorId}`,
@@ -146,33 +158,31 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
// Step 6. Handle notifications (best effort, continue even in event of failure, don't wait) // Step 6. Handle notifications (best effort, continue even in event of failure, don't wait)
if (decision.shouldSendNotification) { if (decision.shouldSendNotification) {
this.notificationsService.handleNotifications(statusChangeResult.monitor, status, decision).catch((error: any) => { this.notificationsService.handleNotifications(statusChangeResult.monitor, status, decision).catch((error: unknown) => {
this.logger.error({ this.logger.error({
message: error.message, message: `Error sending notifications for job ${statusChangeResult.monitor.id}: ${error instanceof Error ? error.message : "Unknown error"}`,
service: SERVICE_NAME, service: SERVICE_NAME,
method: "getMonitorJob", method: "getMonitorJob",
details: `Error sending notifications for job ${statusChangeResult.monitor.id}: ${error.message}`, stack: error instanceof Error ? error.stack : undefined,
stack: error.stack,
}); });
}); });
} }
// Step 7. Handle incidents (best effort, don't wait) // Step 7. Handle incidents (best effort, don't wait)
this.incidentService.handleIncident(statusChangeResult.monitor, statusChangeResult.code, decision, status).catch((error: any) => { this.incidentService.handleIncident(statusChangeResult.monitor, statusChangeResult.code, decision, status).catch((error: unknown) => {
this.logger.warn({ this.logger.warn({
message: error.message, message: `Error handling incident for job ${monitor.id}: ${error instanceof Error ? error.message : "Unknown error"}`,
service: SERVICE_NAME, service: SERVICE_NAME,
method: "getMonitorJob", method: "getMonitorJob",
details: `Error handling incident for job ${monitor.id}: ${error.message}`, stack: error instanceof Error ? error.stack : undefined,
stack: error.stack,
}); });
}); });
} catch (error: any) { } catch (error: unknown) {
this.logger.warn({ this.logger.warn({
message: error.message, message: error instanceof Error ? error.message : "Unknown error",
service: error.service || SERVICE_NAME, service: SERVICE_NAME,
method: error.method || "getMonitorJob", method: "getMonitorJob",
stack: error.stack, stack: error instanceof Error ? error.stack : undefined,
}); });
throw error; throw error;
} }
@@ -258,19 +268,19 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
service: SERVICE_NAME, service: SERVICE_NAME,
method: "getCleanupOrphanedJob", method: "getCleanupOrphanedJob",
}); });
} catch (error: any) { } catch (error: unknown) {
this.logger.warn({ this.logger.warn({
message: error.message, message: error instanceof Error ? error.message : "Unknown error",
service: SERVICE_NAME, service: SERVICE_NAME,
method: "getCleanupOrphanedJob", method: "getCleanupOrphanedJob",
stack: error.stack, stack: error instanceof Error ? error.stack : undefined,
}); });
throw error; throw error;
} }
}; };
}; };
getGeoCheckJob = () => { getHeartbeatGeoJob = () => {
return async (monitor: Monitor) => { return async (monitor: Monitor) => {
try { try {
const monitorId = monitor.id; const monitorId = monitor.id;
@@ -278,7 +288,7 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
// Step 1: Validate monitor eligibility // Step 1: Validate monitor eligibility
if (!monitorId) { if (!monitorId) {
throw new AppError({ message: "No monitor id", service: SERVICE_NAME, method: "getGeoCheckJob" }); throw new AppError({ message: "No monitor id", service: SERVICE_NAME, method: "getHeartbeatGeoJob" });
} }
if (!monitor.geoCheckEnabled) { if (!monitor.geoCheckEnabled) {
@@ -288,7 +298,7 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
this.logger.debug({ this.logger.debug({
message: `Monitor ${monitorId} type does not support geo checks, skipping`, message: `Monitor ${monitorId} type does not support geo checks, skipping`,
service: SERVICE_NAME, service: SERVICE_NAME,
method: "getGeoCheckJob", method: "getHeartbeatGeoJob",
}); });
return; return;
} }
@@ -297,7 +307,7 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
this.logger.warn({ this.logger.warn({
message: `No geo check locations configured for monitor ${monitorId}`, message: `No geo check locations configured for monitor ${monitorId}`,
service: SERVICE_NAME, service: SERVICE_NAME,
method: "getGeoCheckJob", method: "getHeartbeatGeoJob",
}); });
return; return;
} }
@@ -308,7 +318,7 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
this.logger.debug({ this.logger.debug({
message: `Monitor ${monitorId} is in maintenance window, skipping geo check`, message: `Monitor ${monitorId} is in maintenance window, skipping geo check`,
service: SERVICE_NAME, service: SERVICE_NAME,
method: "getGeoCheckJob", method: "getHeartbeatGeoJob",
}); });
return; return;
} }
@@ -319,7 +329,7 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
this.logger.warn({ this.logger.warn({
message: `No geo check could be built for monitor ${monitorId}`, message: `No geo check could be built for monitor ${monitorId}`,
service: SERVICE_NAME, service: SERVICE_NAME,
method: "getGeoCheckJob", method: "getHeartbeatGeoJob",
}); });
return; return;
} }
@@ -330,14 +340,14 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
this.logger.debug({ this.logger.debug({
message: `Geo check job executed for monitor ${monitorId}`, message: `Geo check job executed for monitor ${monitorId}`,
service: SERVICE_NAME, service: SERVICE_NAME,
method: "getGeoCheckJob", method: "getHeartbeatGeoJob",
}); });
} catch (error: any) { } catch (error: unknown) {
this.logger.error({ this.logger.error({
message: error.message, message: error instanceof Error ? error.message : "Unknown error",
service: SERVICE_NAME, service: SERVICE_NAME,
method: "getGeoCheckJob", method: "getHeartbeatGeoJob",
stack: error.stack, stack: error instanceof Error ? error.stack : undefined,
}); });
// Don't throw - geo check failures shouldn't crash the job scheduler // Don't throw - geo check failures shouldn't crash the job scheduler
} }
@@ -347,7 +357,7 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
async isInMaintenanceWindow(monitorId: string, teamId: string) { async isInMaintenanceWindow(monitorId: string, teamId: string) {
const maintenanceWindows = await this.maintenanceWindowsRepository.findByMonitorId(monitorId, teamId); const maintenanceWindows = await this.maintenanceWindowsRepository.findByMonitorId(monitorId, teamId);
// Check for active maintenance window: // Check for active maintenance window:
const maintenanceWindowIsActive = maintenanceWindows.reduce((acc: any, window: any) => { const maintenanceWindowIsActive = maintenanceWindows.reduce((acc: boolean, window: MaintenanceWindow) => {
if (window.active) { if (window.active) {
const start = new Date(window.start); const start = new Date(window.start);
const end = new Date(window.end); const end = new Date(window.end);
@@ -374,6 +384,31 @@ export class SuperSimpleQueueHelper implements ISuperSimpleQueueHelper {
return maintenanceWindowIsActive; return maintenanceWindowIsActive;
} }
getCleanupRetentionJob = () => {
return async () => {
try {
const settings = await this.settingsService.getDBSettings();
const checkTTL = settings.checkTTL; // Check TTL is in DAYS, not MS
const checkTTLInMs = checkTTL * 24 * 60 * 60 * 1000;
const cutoffDate = new Date(Date.now() - checkTTLInMs);
const deleteCount = await this.checkService.deleteOlderThan(cutoffDate);
this.logger.info({
message: `Deleted ${deleteCount} checks older than ${cutoffDate.toISOString()}`,
service: SERVICE_NAME,
method: "getCleanupRetentionJob",
});
} catch (error: unknown) {
this.logger.error({
message: error instanceof Error ? error.message : "Unknown error",
service: SERVICE_NAME,
method: "getCleanupRetentionJob",
stack: error instanceof Error ? error.stack : undefined,
});
}
};
};
private evaluateMonitorAction(statusChangeResult: StatusChangeResult): MonitorActionDecision { private evaluateMonitorAction(statusChangeResult: StatusChangeResult): MonitorActionDecision {
const { monitor, statusChanged, prevStatus } = statusChangeResult; const { monitor, statusChanged, prevStatus } = statusChangeResult;
@@ -1,4 +1,4 @@
import type { Monitor, MonitorStatusResponse } from "@/types/index.js"; import type { Monitor, MonitorStatusResponse, MonitorPayloadMap, MonitorType } from "@/types/index.js";
import type { AxiosStatic } from "axios"; import type { AxiosStatic } from "axios";
import { AppError } from "@/utils/AppError.js"; import { AppError } from "@/utils/AppError.js";
import { NETWORK_ERROR } from "@/service/infrastructure/network/utils.js"; import { NETWORK_ERROR } from "@/service/infrastructure/network/utils.js";
@@ -8,7 +8,7 @@ const SERVICE_NAME = "NetworkService";
export interface INetworkService { export interface INetworkService {
readonly serviceName: string; readonly serviceName: string;
requestStatus(monitor: Monitor): Promise<MonitorStatusResponse<unknown>>; requestStatus<T extends MonitorType>(monitor: Monitor & { type: T }): Promise<MonitorStatusResponse<MonitorPayloadMap[T]>>;
requestWebhook( requestWebhook(
type: string, type: string,
url: string, url: string,
@@ -43,12 +43,12 @@ export class NetworkService implements INetworkService {
} }
// Main entry point // Main entry point
async requestStatus(monitor: Monitor) { async requestStatus<T extends MonitorType>(monitor: Monitor & { type: T }): Promise<MonitorStatusResponse<MonitorPayloadMap[T]>> {
const provider = this.providers.find((p) => p.supports(monitor.type)); const provider = this.providers.find((p) => p.supports(monitor.type));
if (!provider) { if (!provider) {
return this.handleUnsupportedType(monitor.type); return this.handleUnsupportedType(monitor.type) as Promise<MonitorStatusResponse<MonitorPayloadMap[T]>>;
} }
return provider.handle(monitor); return provider.handle(monitor) as Promise<MonitorStatusResponse<MonitorPayloadMap[T]>>;
} }
private async handleUnsupportedType(type: string): Promise<MonitorStatusResponse> { private async handleUnsupportedType(type: string): Promise<MonitorStatusResponse> {
+1 -4
View File
@@ -98,8 +98,6 @@ export interface Check {
timings?: GotTimings; timings?: GotTimings;
statusCode: number; statusCode: number;
message: string; message: string;
ack: boolean;
ackAt?: string | null;
expiry: string; expiry: string;
cpu?: CheckCpuInfo; cpu?: CheckCpuInfo;
memory?: CheckMemoryInfo; memory?: CheckMemoryInfo;
@@ -113,7 +111,6 @@ export interface Check {
seo?: number; seo?: number;
performance?: number; performance?: number;
audits?: CheckAudits; audits?: CheckAudits;
__v: number;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
} }
@@ -205,4 +202,4 @@ export type NormalizedUptimeCheck<T extends GroupedCheck = GroupedCheck> = T & {
originalAvgResponseTime: number; originalAvgResponseTime: number;
}; };
export type CheckSnapshot = Omit<Check, "metadata" | "ack" | "ackAt" | "expiry" | "__v" | "updatedAt">; export type CheckSnapshot = Omit<Check, "metadata" | "expiry" | "updatedAt">;
+1 -1
View File
@@ -126,7 +126,7 @@ export interface MonitorPayloadMap {
game: GameStatusPayload; game: GameStatusPayload;
grpc: GrpcStatusPayload; grpc: GrpcStatusPayload;
websocket: WebSocketStatusPayload; websocket: WebSocketStatusPayload;
default: unknown; unknown: unknown;
} }
export type StatusChangeResult = { export type StatusChangeResult = {