diff --git a/server/src/controllers/incidentController.ts b/server/src/controllers/incidentController.ts index 0eaba7173..4e6242184 100644 --- a/server/src/controllers/incidentController.ts +++ b/server/src/controllers/incidentController.ts @@ -1,35 +1,30 @@ import { AppError } from "@/utils/AppError.js"; import { Request, Response, NextFunction } from "express"; import { requireTeamId, requireUserId, requireUserEmail } from "./controllerUtils.js"; +import { IIncidentService } from "@/service/index.js"; +import { getIncidentsByTeamQueryValidation, getIncidentSummaryQueryValidation } from "@/validation/incidentValidation.js"; -const SERVICE_NAME = "incidentController"; - +const SERVICE_NAME = "IncidentController"; class IncidentController { - static SERVICE_NAME = SERVICE_NAME; - - private incidentService: any; - constructor(incidentService: any) { + private incidentService: IIncidentService; + constructor(incidentService: IIncidentService) { this.incidentService = incidentService; } - get serviceName() { - return IncidentController.SERVICE_NAME; - } - getIncidentsByTeam = async (req: Request, res: Response, next: NextFunction) => { try { - const { sortOrder, dateRange, page, rowsPerPage, status, monitorId, resolutionType } = req.query || {}; + const validatedQuery = getIncidentsByTeamQueryValidation.parse(req.query); const teamId = requireTeamId(req.user?.teamId); const result = await this.incidentService.getIncidentsByTeam( teamId, - sortOrder, - dateRange, - page, - rowsPerPage, - status, - monitorId, - resolutionType + validatedQuery.sortOrder, + validatedQuery.dateRange, + validatedQuery.page, + validatedQuery.rowsPerPage, + validatedQuery.status, + validatedQuery.monitorId, + validatedQuery.resolutionType ); return res.status(200).json({ @@ -45,10 +40,9 @@ class IncidentController { getIncidentSummary = async (req: Request, res: Response, next: NextFunction) => { try { const teamId = requireTeamId(req.user?.teamId); + const validatedQuery = getIncidentSummaryQueryValidation.parse(req.query); - const limit = req.query.limit ? parseInt(req.query.limit as string, 10) : 10; - - const summary = await this.incidentService.getIncidentSummary(teamId, limit); + const summary = await this.incidentService.getIncidentSummary(teamId, validatedQuery.limit); return res.status(200).json({ success: true, msg: "Incident summary retrieved successfully", diff --git a/server/src/service/business/incidentService.ts b/server/src/service/business/incidentService.ts index 710336016..4790f9aac 100644 --- a/server/src/service/business/incidentService.ts +++ b/server/src/service/business/incidentService.ts @@ -2,7 +2,6 @@ const SERVICE_NAME = "incidentService"; import type { Monitor } from "@/types/monitor.js"; import type { MonitorStatusResponse } from "@/types/network.js"; import { AppError } from "@/utils/AppError.js"; -import { ParseBoolean } from "@/utils/utils.js"; import { getDateForRange } from "@/utils/dataUtils.js"; import type { IIncidentsRepository, IMonitorsRepository, IUsersRepository } from "@/repositories/index.js"; import type { Incident, IncidentSummary, User } from "@/types/index.js"; @@ -22,13 +21,13 @@ export interface IIncidentService { teamId: string, sortOrder: string, dateRange: string, - page: string, - rowsPerPage: string, - status: string, - monitorId: string, - resolutionType: string + page: number, + rowsPerPage: number, + status: boolean | undefined, + monitorId: string | undefined, + resolutionType: string | undefined ): Promise<{ incidents: Incident[]; count: number }>; - getIncidentSummary(teamId: string, limit?: string): Promise; + getIncidentSummary(teamId: string, limit?: number): Promise; getIncidentById(incidentId: string, teamId: string): Promise<{ incident: Incident; monitor: Monitor; user: User | null }>; } @@ -164,13 +163,13 @@ export class IncidentService implements IIncidentService { }); return resolvedIncident; - } catch (error: any) { + } catch (error: unknown) { this.logger.error({ service: SERVICE_NAME, method: "resolveIncident", - message: error.message, + message: error instanceof Error ? error.message : "Unknown error", details: { id: incidentId }, - stack: error.stack, + stack: error instanceof Error ? error.stack : undefined, }); throw error; } @@ -180,11 +179,11 @@ export class IncidentService implements IIncidentService { teamId: string, sortOrder: string, dateRange: string, - page: string, - rowsPerPage: string, - status: string, - monitorId: string, - resolutionType: string + page: number, + rowsPerPage: number, + status: boolean | undefined, + monitorId: string | undefined, + resolutionType: string | undefined ) => { try { if (!teamId) { @@ -193,9 +192,8 @@ export class IncidentService implements IIncidentService { const startDate = getDateForRange(dateRange); - const parsedPage = Number.isFinite(parseInt(page)) ? parseInt(page) : 0; - const parsedRowsPerPage = Number.isFinite(parseInt(rowsPerPage)) ? parseInt(rowsPerPage) : 20; - const parsedStatus = typeof status === "undefined" ? undefined : ParseBoolean(status); + const parsedPage = page ?? 0; + const parsedRowsPerPage = rowsPerPage ?? 20; const incidents = await this.incidentsRepository.findByTeamId( teamId, @@ -203,43 +201,43 @@ export class IncidentService implements IIncidentService { parsedPage, parsedRowsPerPage, sortOrder, - parsedStatus, + status, monitorId, resolutionType ); - const count = await this.incidentsRepository.countByTeamId(teamId, startDate, parsedStatus, monitorId, resolutionType); + const count = await this.incidentsRepository.countByTeamId(teamId, startDate, status, monitorId, resolutionType); return { incidents, count }; - } catch (error: any) { + } catch (error: unknown) { this.logger.error({ service: SERVICE_NAME, method: "getIncidentsByTeam", - message: error.message, + message: error instanceof Error ? error.message : "Unknown error", details: { teamId }, - stack: error.stack, + stack: error instanceof Error ? error.stack : undefined, }); throw error; } }; - getIncidentSummary = async (teamId: string, limit?: string) => { + getIncidentSummary = async (teamId: string, limit?: number) => { try { if (!teamId) { throw new AppError({ message: "No team ID in request", service: SERVICE_NAME, method: "getIncidentSummary", status: 400 }); } - const parsedLimit = limit && Number.isFinite(parseInt(limit, 10)) ? parseInt(limit, 10) : 10; + const parsedLimit = limit ?? 10; const summary = await this.incidentsRepository.findSummaryByTeamId(teamId, parsedLimit); return summary; - } catch (error: any) { + } catch (error: unknown) { this.logger.error({ service: SERVICE_NAME, method: "getIncidentSummary", - message: error.message, + message: error instanceof Error ? error.message : "Unknown error", details: { teamId }, - stack: error.stack, + stack: error instanceof Error ? error.stack : undefined, }); throw error; } @@ -254,13 +252,13 @@ export class IncidentService implements IIncidentService { user = await this.usersRepository.findById(incident.resolvedBy); } return { incident, monitor, user }; - } catch (error: any) { + } catch (error: unknown) { this.logger.error({ service: SERVICE_NAME, method: "getIncidentById", - message: error.message, + message: error instanceof Error ? error.message : "Unknown error", details: { incidentId }, - stack: error.stack, + stack: error instanceof Error ? error.stack : undefined, }); throw error; } diff --git a/server/src/validation/incidentValidation.ts b/server/src/validation/incidentValidation.ts new file mode 100644 index 000000000..acbc857dc --- /dev/null +++ b/server/src/validation/incidentValidation.ts @@ -0,0 +1,20 @@ +import { z } from "zod"; +import { booleanCoercion } from "./shared.js"; + +//**************************************** +// Incident Validations +//**************************************** + +export const getIncidentsByTeamQueryValidation = z.object({ + sortOrder: z.enum(["asc", "desc"]), + dateRange: z.enum(["recent", "hour", "day", "week", "month", "all"]), + page: z.coerce.number().int().min(0), + rowsPerPage: z.coerce.number().int().min(1), + status: booleanCoercion.optional(), + monitorId: z.string().optional(), + resolutionType: z.enum(["manual", "automatic"]).optional(), +}); + +export const getIncidentSummaryQueryValidation = z.object({ + limit: z.coerce.number().int().min(1).optional(), +}); diff --git a/server/src/validation/index.ts b/server/src/validation/index.ts index 7439f043d..cc1d33372 100644 --- a/server/src/validation/index.ts +++ b/server/src/validation/index.ts @@ -20,3 +20,4 @@ export * from "./settingsValidation.js"; export * from "./statusPageValidation.js"; export * from "./notificationValidation.js"; export * from "./userValidation.js"; +export * from "./incidentValidation.js";