From 6101fe1bfa2eafd349f534775d2de6b0ea406dcb Mon Sep 17 00:00:00 2001 From: Br0wnHammer Date: Wed, 21 Jan 2026 01:42:00 +0530 Subject: [PATCH 01/13] Fix: Configure Pause Button --- client/src/Components/v1/MonitorDetailsControlHeader/index.jsx | 2 +- client/src/Pages/Uptime/Create/index.jsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx b/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx index c5b62280f..0793e7149 100644 --- a/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx +++ b/client/src/Components/v1/MonitorDetailsControlHeader/index.jsx @@ -124,7 +124,7 @@ const MonitorDetailsControlHeader = ({ }); }} > - {monitor?.isActive ? "Pause" : "Resume"} + {monitor?.isActive ? t("pause") : t("resume")} )} {isAdmin && ( diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx index b1460dba6..1d0aa64f4 100644 --- a/client/src/Pages/Uptime/Create/index.jsx +++ b/client/src/Pages/Uptime/Create/index.jsx @@ -285,6 +285,7 @@ const UptimeCreate = ({ isClone = false }) => { const handlePause = async () => { await pauseMonitor({ monitorId, triggerUpdate }); + setMonitor((prev) => ({ ...prev, isActive: !prev.isActive })); }; const handleRemove = async (event) => { From dd259fea8924790065c3371eb127cc668ca5d3ca Mon Sep 17 00:00:00 2001 From: Br0wnHammer Date: Wed, 21 Jan 2026 02:02:16 +0530 Subject: [PATCH 02/13] Fix: One source of truth --- client/src/Hooks/monitorHooks.js | 9 +++++++-- client/src/Pages/Uptime/Create/index.jsx | 9 ++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/client/src/Hooks/monitorHooks.js b/client/src/Hooks/monitorHooks.js index 9d46e9365..f0bf805f4 100644 --- a/client/src/Hooks/monitorHooks.js +++ b/client/src/Hooks/monitorHooks.js @@ -412,14 +412,19 @@ export const usePauseMonitor = () => { try { setIsLoading(true); const res = await networkService.pauseMonitorById({ monitorId }); + const updatedMonitor = res.data.data; createToast({ - body: res.data.data.isActive + body: updatedMonitor.isActive ? "Monitor resumed successfully" : "Monitor paused successfully", }); - triggerUpdate(); + if (triggerUpdate) { + triggerUpdate(); + } + return updatedMonitor; } catch (error) { setError(error); + return null; } finally { setIsLoading(false); } diff --git a/client/src/Pages/Uptime/Create/index.jsx b/client/src/Pages/Uptime/Create/index.jsx index 1d0aa64f4..6688f9b12 100644 --- a/client/src/Pages/Uptime/Create/index.jsx +++ b/client/src/Pages/Uptime/Create/index.jsx @@ -284,8 +284,10 @@ const UptimeCreate = ({ isClone = false }) => { }; const handlePause = async () => { - await pauseMonitor({ monitorId, triggerUpdate }); - setMonitor((prev) => ({ ...prev, isActive: !prev.isActive })); + const updatedMonitor = await pauseMonitor({ monitorId }); + if (updatedMonitor) { + setMonitor((prev) => ({ ...prev, isActive: updatedMonitor.isActive })); + } }; const handleRemove = async (event) => { @@ -425,7 +427,8 @@ const UptimeCreate = ({ isClone = false }) => { From 3fa370765e7b3af770ad9ec66b6381da154a067a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 21 Jan 2026 09:52:31 -0800 Subject: [PATCH 05/13] purge papaparse --- server/package-lock.json | 7 -- server/package.json | 1 - server/src/config/services.ts | 2 - server/src/controllers/incidentController.ts | 1 + server/src/controllers/monitorController.ts | 22 +--- server/src/service/business/monitorService.ts | 107 +++++++----------- 6 files changed, 48 insertions(+), 92 deletions(-) diff --git a/server/package-lock.json b/server/package-lock.json index a1a392b18..2dd8d23e7 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -36,7 +36,6 @@ "mongoose": "^8.3.3", "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.14", - "papaparse": "^5.5.2", "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", @@ -11567,12 +11566,6 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, - "node_modules/papaparse": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", - "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", - "license": "MIT" - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", diff --git a/server/package.json b/server/package.json index 2bf898c94..2cbacea39 100755 --- a/server/package.json +++ b/server/package.json @@ -48,7 +48,6 @@ "mongoose": "^8.3.3", "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.14", - "papaparse": "^5.5.2", "ping": "0.4.4", "sharp": "0.33.5", "ssl-checker": "2.0.10", diff --git a/server/src/config/services.ts b/server/src/config/services.ts index de4d5115f..423e23ca5 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -24,7 +24,6 @@ import InviteService from "../service/business/inviteService.js"; import MaintenanceWindowService from "../service/business/maintenanceWindowService.js"; import { MonitorService } from "@/service/index.js"; import IncidentService from "../service/business/incidentService.js"; -import papaparse from "papaparse"; import axios from "axios"; import got from "got"; import ping from "ping"; @@ -276,7 +275,6 @@ export const initializeServices = async ({ const monitorService = new MonitorService({ jobQueue: superSimpleQueue, emailService, - papaparse, logger, errorService, games, diff --git a/server/src/controllers/incidentController.ts b/server/src/controllers/incidentController.ts index 93783b20c..a5477e019 100644 --- a/server/src/controllers/incidentController.ts +++ b/server/src/controllers/incidentController.ts @@ -73,6 +73,7 @@ class IncidentController { resolveIncidentManually = async (req: Request, res: Response, next: NextFunction) => { try { + console.log({ user: req.user }); const resolvedIncident = await this.incidentService.resolveIncident( req?.params?.incidentId, req?.user?.id, diff --git a/server/src/controllers/monitorController.ts b/server/src/controllers/monitorController.ts index 2ebb113ae..30ceabbc5 100644 --- a/server/src/controllers/monitorController.ts +++ b/server/src/controllers/monitorController.ts @@ -174,27 +174,15 @@ class MonitorController { createBulkMonitors = async (req: Request, res: Response, next: NextFunction) => { try { - if (!req.file) { - throw new AppError({ message: "No file uploaded", status: 400 }); + const userId = req.user?.id; + if (!userId) { + throw new AppError({ message: "User ID is required", status: 400 }); } - if (!req.file.mimetype.includes("csv")) { - throw new AppError({ message: "File is not a CSV", status: 400 }); - } - - if (req.file.size === 0) { - throw new AppError({ message: "File is empty", status: 400 }); - } - - const userId = requireString(req?.user?.id, "User ID"); const teamId = requireTeamId(req?.user?.teamId); - const fileData = req?.file?.buffer?.toString("utf-8"); - if (!fileData) { - throw new AppError({ message: "Cannot get file from buffer", status: 400 }); - } - - const monitors = await this.monitorService.createBulkMonitors(fileData, userId, teamId); + const data = req.body; + const monitors = await this.monitorService.createBulkMonitors(data, userId, teamId); return res.status(200).json({ success: true, diff --git a/server/src/service/business/monitorService.ts b/server/src/service/business/monitorService.ts index 4972bdee7..4f97bdf0f 100644 --- a/server/src/service/business/monitorService.ts +++ b/server/src/service/business/monitorService.ts @@ -1,7 +1,13 @@ import { createMonitorsBodyValidation } from "@/validation/joi.js"; import { NormalizeData, NormalizeDataUptimeDetails } from "@/utils/dataUtils.js"; import { type Monitor } from "@/types/index.js"; -import type { MonitorType, MonitorsWithChecksByTeamIdResult, UptimeDetailsResult, HardwareDetailsResult, PageSpeedDetailsResult } from "@/types/monitor.js"; +import type { + MonitorType, + MonitorsWithChecksByTeamIdResult, + UptimeDetailsResult, + HardwareDetailsResult, + PageSpeedDetailsResult, +} from "@/types/monitor.js"; import type { IChecksRepository, IMonitorsRepository, IMonitorStatsRepository, IStatusPagesRepository } from "@/repositories/index.js"; import fs from "fs"; import { fileURLToPath } from "url"; @@ -18,7 +24,7 @@ export interface IMonitorService { // create createMonitor(teamId: string, userId: string, body: Monitor): Promise; - createBulkMonitors(fileData: string, userId: string, teamId: string): Promise; + createBulkMonitors(monitors: Array, userId: string, teamId: string): Promise; addDemoMonitors(args: { userId: string; teamId: string }): Promise; // read @@ -68,7 +74,6 @@ export class MonitorService implements IMonitorService { private jobQueue: ISuperSimpleQueue; private emailService: any; - private papaparse: any; private logger: any; private errorService: any; private games: any; @@ -80,7 +85,6 @@ export class MonitorService implements IMonitorService { constructor({ jobQueue, emailService, - papaparse, logger, errorService, games, @@ -91,7 +95,6 @@ export class MonitorService implements IMonitorService { }: { jobQueue: ISuperSimpleQueue; emailService: any; - papaparse: any; logger: any; errorService: any; games: any; @@ -102,7 +105,6 @@ export class MonitorService implements IMonitorService { }) { this.jobQueue = jobQueue; this.emailService = emailService; - this.papaparse = papaparse; this.logger = logger; this.errorService = errorService; this.games = games; @@ -150,63 +152,14 @@ export class MonitorService implements IMonitorService { this.jobQueue.addJob(monitor.id, monitor); }; - createBulkMonitors = async (fileData: string, userId: string, teamId: string): Promise => { - const { parse } = this.papaparse; + createBulkMonitors = async (monitors: Array, userId: string, teamId: string): Promise => { + const createdMonitors = await this.monitorsRepository.createBulkMonitors(monitors); + if (!monitors || monitors.length === 0) { + throw new AppError("Failed to create monitors", 500); + } - return new Promise((resolve, reject) => { - parse(fileData, { - header: true, - skipEmptyLines: true, - transform: (value: string, header: string): string | number | undefined => { - if (value === "") return undefined; // Empty fields become undefined - - // Handle 'port' and 'interval' fields, check if they're valid numbers - if (["port", "interval"].includes(header)) { - const num = parseInt(value, 10); - if (isNaN(num)) { - throw this.errorService.createBadRequestError(`${header} should be a valid number, got: ${value}`); - } - return num; - } - - return value; - }, - complete: async ({ data, errors }: { data: any[]; errors: Error[] }): Promise => { - try { - if (errors.length > 0) { - throw this.errorService.createServerError("Error parsing CSV"); - } - - if (!data || data.length === 0) { - throw this.errorService.createServerError("CSV file contains no data rows"); - } - - const enrichedData = data.map((monitor: Monitor) => ({ - ...monitor, - userId, - teamId, - description: monitor.description || monitor.name || monitor.url, - name: monitor.name || monitor.url, - type: monitor.type || "http", - })); - - await createMonitorsBodyValidation.validateAsync(enrichedData); - - const monitors = await this.monitorsRepository.createBulkMonitors(enrichedData); - - await Promise.all( - monitors.map(async (monitor: Monitor) => { - this.jobQueue.addJob(monitor.id, monitor); - }) - ); - - resolve(monitors); - } catch (error) { - reject(error); - } - }, - }); - }); + await Promise.all(createdMonitors.map((monitor) => this.jobQueue.addJob(monitor.id, monitor))); + return createdMonitors; }; addDemoMonitors = async ({ userId, teamId }: { userId: string; teamId: string }): Promise => { @@ -277,7 +230,15 @@ export class MonitorService implements IMonitorService { }; }; - getHardwareDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise => { + getHardwareDetailsById = async ({ + teamId, + monitorId, + dateRange, + }: { + teamId: string; + monitorId: string; + dateRange: string; + }): Promise => { const monitor = await this.monitorsRepository.findById(monitorId, teamId); if (!monitor) { throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 }); @@ -308,7 +269,15 @@ export class MonitorService implements IMonitorService { }; }; - getPageSpeedDetailsById = async ({ teamId, monitorId, dateRange }: { teamId: string; monitorId: string; dateRange: string }): Promise => { + getPageSpeedDetailsById = async ({ + teamId, + monitorId, + dateRange, + }: { + teamId: string; + monitorId: string; + dateRange: string; + }): Promise => { const monitor = await this.monitorsRepository.findById(monitorId, teamId); if (!monitor) { throw new AppError({ message: `Monitor with ID ${monitorId} not found.`, status: 404 }); @@ -342,7 +311,15 @@ export class MonitorService implements IMonitorService { return monitor; }; - getMonitorsByTeamId = async ({ teamId, type, filter }: { teamId: string; type?: MonitorType | MonitorType[]; filter?: string }): Promise => { + getMonitorsByTeamId = async ({ + teamId, + type, + filter, + }: { + teamId: string; + type?: MonitorType | MonitorType[]; + filter?: string; + }): Promise => { const monitors = await this.monitorsRepository.findByTeamId(teamId, { type, filter, From ef61a63da09526b5d9f1de4c9645f9e0c82c9c30 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 21 Jan 2026 10:15:13 -0800 Subject: [PATCH 06/13] parse query --- server/src/controllers/incidentController.ts | 19 ++++++++++++++----- .../src/service/business/incidentService.ts | 12 ++++++++++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/server/src/controllers/incidentController.ts b/server/src/controllers/incidentController.ts index a5477e019..6231f3cf4 100644 --- a/server/src/controllers/incidentController.ts +++ b/server/src/controllers/incidentController.ts @@ -1,5 +1,6 @@ import { AppError } from "@/utils/AppError.js"; import { Request, Response, NextFunction } from "express"; +import { requireTeamId } from "./controllerUtils.js"; const SERVICE_NAME = "incidentController"; @@ -17,10 +18,19 @@ class IncidentController { getIncidentsByTeam = async (req: Request, res: Response, next: NextFunction) => { try { - const result = await this.incidentService.getIncidentsByTeam({ - teamId: req?.user?.teamId, - query: req?.query, - }); + const { sortOrder, dateRange, page, rowsPerPage, status, monitorId, resolutionType } = req.query || {}; + + const teamId = requireTeamId(req.user?.teamId); + const result = await this.incidentService.getIncidentsByTeam( + teamId, + sortOrder, + dateRange, + page, + rowsPerPage, + status, + monitorId, + resolutionType + ); return res.status(200).json({ success: true, @@ -73,7 +83,6 @@ class IncidentController { resolveIncidentManually = async (req: Request, res: Response, next: NextFunction) => { try { - console.log({ user: req.user }); const resolvedIncident = await this.incidentService.resolveIncident( req?.params?.incidentId, req?.user?.id, diff --git a/server/src/service/business/incidentService.ts b/server/src/service/business/incidentService.ts index 5ababc5b4..e3fb4cfc3 100644 --- a/server/src/service/business/incidentService.ts +++ b/server/src/service/business/incidentService.ts @@ -124,13 +124,21 @@ class IncidentService { } }; - getIncidentsByTeam = async ({ teamId, query }: { teamId: string; query?: any }) => { + getIncidentsByTeam = async ( + teamId: string, + sortOrder: string, + dateRange: string, + page: string, + rowsPerPage: string, + status: string, + monitorId: string, + resolutionType: string + ) => { try { if (!teamId) { throw this.errorService.createBadRequestError("No team ID in request"); } - const { sortOrder, dateRange, page, rowsPerPage, status, monitorId, resolutionType } = query || {}; const startDate = dateRangeLookup[dateRange]; const parsedPage = Number.isFinite(parseInt(page)) ? parseInt(page) : 0; From d242382abfc0428078bfda6abe5df96ddc1716cb Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 21 Jan 2026 10:16:17 -0800 Subject: [PATCH 07/13] format --- server/src/repositories/monitors/MongoMonitorsRepository.ts | 5 +---- server/src/utils/dataUtils.ts | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/server/src/repositories/monitors/MongoMonitorsRepository.ts b/server/src/repositories/monitors/MongoMonitorsRepository.ts index 4a659b1c2..17b9ce243 100644 --- a/server/src/repositories/monitors/MongoMonitorsRepository.ts +++ b/server/src/repositories/monitors/MongoMonitorsRepository.ts @@ -146,10 +146,7 @@ class MongoMonitorsRepository implements IMonitorsRepository { return { monitors: this.mapDocuments(monitors), deletedCount }; }; - findMonitorsSummaryByTeamId = async ( - teamId: string, - config?: SummaryConfig - ): Promise => { + findMonitorsSummaryByTeamId = async (teamId: string, config?: SummaryConfig): Promise => { const match: FilterQuery = { teamId: new mongoose.Types.ObjectId(teamId) }; if (config?.type !== undefined) { match.type = Array.isArray(config.type) ? { $in: config.type } : config.type; diff --git a/server/src/utils/dataUtils.ts b/server/src/utils/dataUtils.ts index fac7cecef..0bf3e6416 100755 --- a/server/src/utils/dataUtils.ts +++ b/server/src/utils/dataUtils.ts @@ -1,6 +1,5 @@ import type { Check, GroupedCheck, NormalizedCheck, NormalizedUptimeCheck } from "@/types/index.js"; - const calculatePercentile = (arr: Check[], percentile: number): number => { const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime); const index = (percentile / 100) * (sorted.length - 1); From 81c7c992e2b8b5bb56f272a716da17a265b734ae Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 21 Jan 2026 10:36:03 -0800 Subject: [PATCH 08/13] routes -> ts --- server/src/config/routes.ts | 25 +++++++++---------- .../routes/{v1/authRoute.js => authRoute.ts} | 11 +++++--- .../{v1/checkRoute.js => checkRoute.ts} | 7 ++++-- .../diagnosticRoute.js => diagnosticRoute.ts} | 11 +++++--- .../{v1/incidentRoute.js => incidentRoute.ts} | 7 ++++-- .../{v1/inviteRoute.js => inviteRoute.ts} | 11 +++++--- .../routes/{v1/logRoutes.js => logRoutes.ts} | 7 ++++-- ...ndowRoute.js => maintenanceWindowRoute.ts} | 5 +++- server/src/routes/{v1 => }/monitorRoute.ts | 4 +-- ...ificationRoute.js => notificationRoute.ts} | 5 +++- .../{v1/queueRoute.js => queueRoute.ts} | 7 ++++-- .../{v1/settingsRoute.js => settingsRoute.ts} | 7 ++++-- .../statusPageRoute.js => statusPageRoute.ts} | 9 ++++--- 13 files changed, 74 insertions(+), 42 deletions(-) rename server/src/routes/{v1/authRoute.js => authRoute.ts} (83%) rename server/src/routes/{v1/checkRoute.js => checkRoute.ts} (83%) rename server/src/routes/{v1/diagnosticRoute.js => diagnosticRoute.ts} (54%) rename server/src/routes/{v1/incidentRoute.js => incidentRoute.ts} (80%) rename server/src/routes/{v1/inviteRoute.js => inviteRoute.ts} (64%) rename server/src/routes/{v1/logRoutes.js => logRoutes.ts} (68%) rename server/src/routes/{v1/maintenanceWindowRoute.js => maintenanceWindowRoute.ts} (87%) rename server/src/routes/{v1 => }/monitorRoute.ts (94%) rename server/src/routes/{v1/notificationRoute.js => notificationRoute.ts} (88%) rename server/src/routes/{v1/queueRoute.js => queueRoute.ts} (84%) rename server/src/routes/{v1/settingsRoute.js => settingsRoute.ts} (77%) rename server/src/routes/{v1/statusPageRoute.js => statusPageRoute.ts} (78%) diff --git a/server/src/config/routes.ts b/server/src/config/routes.ts index 024387c25..d38ac69ae 100644 --- a/server/src/config/routes.ts +++ b/server/src/config/routes.ts @@ -1,23 +1,22 @@ import { createVerifyJWT } from "../middleware/verifyJWT.js"; import { authApiLimiter } from "../middleware/rateLimiter.js"; -import AuthRoutes from "../routes/v1/authRoute.js"; -import InviteRoutes from "../routes/v1/inviteRoute.js"; -import MonitorRoutes from "../routes/v1/monitorRoute.js"; -import CheckRoutes from "../routes/v1/checkRoute.js"; -import SettingsRoutes from "../routes/v1/settingsRoute.js"; -import MaintenanceWindowRoutes from "../routes/v1/maintenanceWindowRoute.js"; -import StatusPageRoutes from "../routes/v1/statusPageRoute.js"; -import QueueRoutes from "../routes/v1/queueRoute.js"; -import LogRoutes from "../routes/v1/logRoutes.js"; -import DiagnosticRoutes from "../routes/v1/diagnosticRoute.js"; -import NotificationRoutes from "../routes/v1/notificationRoute.js"; +import AuthRoutes from "../routes/authRoute.js"; +import InviteRoutes from "../routes/inviteRoute.js"; +import MonitorRoutes from "../routes/monitorRoute.js"; +import CheckRoutes from "../routes/checkRoute.js"; +import SettingsRoutes from "../routes/settingsRoute.js"; +import MaintenanceWindowRoutes from "../routes/maintenanceWindowRoute.js"; +import StatusPageRoutes from "../routes/statusPageRoute.js"; +import QueueRoutes from "../routes/queueRoute.js"; +import LogRoutes from "../routes/logRoutes.js"; +import DiagnosticRoutes from "../routes/diagnosticRoute.js"; +import NotificationRoutes from "../routes/notificationRoute.js"; -import IncidentRoutes from "../routes/v1/incidentRoute.js"; +import IncidentRoutes from "../routes/incidentRoute.js"; export const setupRoutes = (app: any, controllers: Record, services: Record) => { const verifyJWT = createVerifyJWT(services.settingsService); - // V1 const authRoutes = new AuthRoutes(controllers.authController, verifyJWT); const monitorRoutes = new MonitorRoutes(controllers.monitorController); const settingsRoutes = new SettingsRoutes(controllers.settingsController); diff --git a/server/src/routes/v1/authRoute.js b/server/src/routes/authRoute.ts similarity index 83% rename from server/src/routes/v1/authRoute.js rename to server/src/routes/authRoute.ts index 0eca250e5..eec6ea11b 100755 --- a/server/src/routes/v1/authRoute.js +++ b/server/src/routes/authRoute.ts @@ -1,17 +1,20 @@ -import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { Router, RequestHandler } from "express"; +import { isAllowed } from "../middleware/isAllowed.js"; import multer from "multer"; const upload = multer(); class AuthRoutes { - constructor(authController, verifyJWT) { + private router: Router; + private authController: any; + + constructor(authController: any, verifyJWT: RequestHandler) { this.router = Router(); this.authController = authController; this.initRoutes(verifyJWT); } - initRoutes(verifyJWT) { + initRoutes(verifyJWT: RequestHandler) { this.router.post("/register", upload.single("profileImage"), this.authController.registerUser); this.router.post("/login", this.authController.loginUser); diff --git a/server/src/routes/v1/checkRoute.js b/server/src/routes/checkRoute.ts similarity index 83% rename from server/src/routes/v1/checkRoute.js rename to server/src/routes/checkRoute.ts index 2c34049bb..4df286846 100755 --- a/server/src/routes/v1/checkRoute.js +++ b/server/src/routes/checkRoute.ts @@ -1,9 +1,12 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../middleware/isAllowed.js"; class CheckRoutes { - constructor(checkController) { + private router: Router; + private checkController: any; + + constructor(checkController: any) { this.router = Router(); this.checkController = checkController; this.initRoutes(); diff --git a/server/src/routes/v1/diagnosticRoute.js b/server/src/routes/diagnosticRoute.ts similarity index 54% rename from server/src/routes/v1/diagnosticRoute.js rename to server/src/routes/diagnosticRoute.ts index 9ec470e53..b2cefe4aa 100755 --- a/server/src/routes/v1/diagnosticRoute.js +++ b/server/src/routes/diagnosticRoute.ts @@ -1,14 +1,17 @@ -import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { RequestHandler, Router } from "express"; +import { isAllowed } from "../middleware/isAllowed.js"; class DiagnosticRoutes { - constructor(diagnosticController, verifyJWT) { + private router: Router; + private diagnosticController: any; + + constructor(diagnosticController: any, verifyJWT: RequestHandler) { this.router = Router(); this.diagnosticController = diagnosticController; this.initRoutes(verifyJWT); } - initRoutes(verifyJWT) { + initRoutes(verifyJWT: RequestHandler) { this.router.get("/system", verifyJWT, isAllowed(["admin", "superadmin"]), this.diagnosticController.getSystemStats); } diff --git a/server/src/routes/v1/incidentRoute.js b/server/src/routes/incidentRoute.ts similarity index 80% rename from server/src/routes/v1/incidentRoute.js rename to server/src/routes/incidentRoute.ts index f87ef17a7..83f1ff8f9 100644 --- a/server/src/routes/v1/incidentRoute.js +++ b/server/src/routes/incidentRoute.ts @@ -1,8 +1,11 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../middleware/isAllowed.js"; class IncidentRoutes { - constructor(incidentController) { + private router: Router; + private incidentController: any; + + constructor(incidentController: any) { this.router = Router(); this.incidentController = incidentController; this.initRoutes(); diff --git a/server/src/routes/v1/inviteRoute.js b/server/src/routes/inviteRoute.ts similarity index 64% rename from server/src/routes/v1/inviteRoute.js rename to server/src/routes/inviteRoute.ts index 44ec0495f..b91f922df 100755 --- a/server/src/routes/v1/inviteRoute.js +++ b/server/src/routes/inviteRoute.ts @@ -1,14 +1,17 @@ -import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { RequestHandler, Router } from "express"; +import { isAllowed } from "../middleware/isAllowed.js"; class InviteRoutes { - constructor(inviteController, verifyJWT) { + private router: Router; + private inviteController: any; + + constructor(inviteController: any, verifyJWT: RequestHandler) { this.router = Router(); this.inviteController = inviteController; this.initRoutes(verifyJWT); } - initRoutes(verifyJWT) { + initRoutes(verifyJWT: RequestHandler) { this.router.post("/send", verifyJWT, isAllowed(["admin", "superadmin"]), this.inviteController.sendInviteEmail); this.router.post("/verify", this.inviteController.verifyInviteToken); this.router.post("/", verifyJWT, isAllowed(["admin", "superadmin"]), this.inviteController.getInviteToken); diff --git a/server/src/routes/v1/logRoutes.js b/server/src/routes/logRoutes.ts similarity index 68% rename from server/src/routes/v1/logRoutes.js rename to server/src/routes/logRoutes.ts index feabe669a..c35361fd0 100755 --- a/server/src/routes/v1/logRoutes.js +++ b/server/src/routes/logRoutes.ts @@ -1,7 +1,10 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../middleware/isAllowed.js"; class LogRoutes { - constructor(logController) { + private router: Router; + private logController: any; + + constructor(logController: any) { this.router = Router(); this.logController = logController; this.initRoutes(); diff --git a/server/src/routes/v1/maintenanceWindowRoute.js b/server/src/routes/maintenanceWindowRoute.ts similarity index 87% rename from server/src/routes/v1/maintenanceWindowRoute.js rename to server/src/routes/maintenanceWindowRoute.ts index 345f2c13c..0230ba4bd 100755 --- a/server/src/routes/v1/maintenanceWindowRoute.js +++ b/server/src/routes/maintenanceWindowRoute.ts @@ -1,7 +1,10 @@ import { Router } from "express"; class MaintenanceWindowRoutes { - constructor(maintenanceWindowController) { + private router: Router; + private mwController: any; + + constructor(maintenanceWindowController: any) { this.router = Router(); this.mwController = maintenanceWindowController; this.initRoutes(); diff --git a/server/src/routes/v1/monitorRoute.ts b/server/src/routes/monitorRoute.ts similarity index 94% rename from server/src/routes/v1/monitorRoute.ts rename to server/src/routes/monitorRoute.ts index 4488a2c49..eb5604c24 100755 --- a/server/src/routes/v1/monitorRoute.ts +++ b/server/src/routes/monitorRoute.ts @@ -1,7 +1,7 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "@/middleware/isAllowed.js"; import multer from "multer"; -import { fetchMonitorCertificate } from "../../controllers/controllerUtils.js"; +import { fetchMonitorCertificate } from "@/controllers/controllerUtils.js"; const upload = multer({ storage: multer.memoryStorage(), // Store file in memory as Buffer }); diff --git a/server/src/routes/v1/notificationRoute.js b/server/src/routes/notificationRoute.ts similarity index 88% rename from server/src/routes/v1/notificationRoute.js rename to server/src/routes/notificationRoute.ts index f88b0b96c..7fe4bf5cb 100755 --- a/server/src/routes/v1/notificationRoute.js +++ b/server/src/routes/notificationRoute.ts @@ -1,6 +1,9 @@ import { Router } from "express"; class NotificationRoutes { - constructor(notificationController) { + private router: Router; + private notificationController: any; + + constructor(notificationController: any) { this.router = Router(); this.notificationController = notificationController; this.initializeRoutes(); diff --git a/server/src/routes/v1/queueRoute.js b/server/src/routes/queueRoute.ts similarity index 84% rename from server/src/routes/v1/queueRoute.js rename to server/src/routes/queueRoute.ts index 59e1cb744..d5b036b15 100755 --- a/server/src/routes/v1/queueRoute.js +++ b/server/src/routes/queueRoute.ts @@ -1,7 +1,10 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../middleware/isAllowed.js"; class QueueRoutes { - constructor(queueController) { + private router: Router; + private queueController: any; + + constructor(queueController: any) { this.router = Router(); this.queueController = queueController; this.initRoutes(); diff --git a/server/src/routes/v1/settingsRoute.js b/server/src/routes/settingsRoute.ts similarity index 77% rename from server/src/routes/v1/settingsRoute.js rename to server/src/routes/settingsRoute.ts index 76556e923..c7db1ed3b 100755 --- a/server/src/routes/v1/settingsRoute.js +++ b/server/src/routes/settingsRoute.ts @@ -1,8 +1,11 @@ import { Router } from "express"; -import { isAllowed } from "../../middleware/isAllowed.js"; +import { isAllowed } from "../middleware/isAllowed.js"; class SettingsRoutes { - constructor(settingsController) { + private router: Router; + private settingsController: any; + + constructor(settingsController: any) { this.router = Router(); this.settingsController = settingsController; this.initRoutes(); diff --git a/server/src/routes/v1/statusPageRoute.js b/server/src/routes/statusPageRoute.ts similarity index 78% rename from server/src/routes/v1/statusPageRoute.js rename to server/src/routes/statusPageRoute.ts index 7d915cb86..9d6902aac 100755 --- a/server/src/routes/v1/statusPageRoute.js +++ b/server/src/routes/statusPageRoute.ts @@ -1,15 +1,18 @@ -import { Router } from "express"; +import { RequestHandler, Router } from "express"; import multer from "multer"; const upload = multer(); class StatusPageRoutes { - constructor(statusPageController, verifyJWT) { + private router: Router; + private statusPageController: any; + + constructor(statusPageController: any, verifyJWT: RequestHandler) { this.router = Router(); this.statusPageController = statusPageController; this.initRoutes(verifyJWT); } - initRoutes(verifyJWT) { + initRoutes(verifyJWT: RequestHandler) { this.router.get("/", this.statusPageController.getStatusPage); this.router.get("/team", verifyJWT, this.statusPageController.getStatusPagesByTeamId); From 2a8d2cb06abf16939f42c30b44bb32e67c3f307f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 21 Jan 2026 11:18:13 -0800 Subject: [PATCH 09/13] app -> ts --- server/package-lock.json | 47 +++++++++++++++++ server/package.json | 4 ++ server/src/{app.js => app.ts} | 25 +++++++-- server/src/config/controllers.ts | 51 +++++++++++-------- server/src/config/index.ts | 25 --------- server/src/config/services.ts | 12 +++-- server/src/{index.js => index.ts} | 6 +-- .../service/infrastructure/bufferService.ts | 6 +-- ...{settingsService.js => settingsService.ts} | 47 +++++++++-------- server/src/utils/logger.ts | 18 +++++-- 10 files changed, 152 insertions(+), 89 deletions(-) rename server/src/{app.js => app.ts} (69%) delete mode 100644 server/src/config/index.ts rename server/src/{index.js => index.ts} (92%) rename server/src/service/system/{settingsService.js => settingsService.ts} (65%) diff --git a/server/package-lock.json b/server/package-lock.json index 2dd8d23e7..3f5b824b0 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -45,6 +45,9 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/compression": "1.8.1", + "@types/cookie-parser": "1.4.10", + "@types/cors": "2.8.19", "@types/dockerode": "^4.0.0", "@types/express": "5.0.3", "@types/gamedig": "^5.0.3", @@ -57,6 +60,7 @@ "@types/nodemailer": "7.0.1", "@types/papaparse": "^5.5.2", "@types/ping": "0.4.4", + "@types/swagger-ui-express": "4.1.8", "c8": "10.1.3", "eslint": "^9.17.0", "eslint-plugin-mocha": "^10.5.0", @@ -4323,6 +4327,17 @@ "@types/node": "*" } }, + "node_modules/@types/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@types/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-kCFuWS0ebDbmxs0AXYn6e2r2nrGAb5KwQhknjSPSPgJcGd8+HVSILlUyFhGqML2gk39HcG7D1ydW9/qpYkN00Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/node": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -4333,6 +4348,26 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.10.tgz", + "integrity": "sha512-B4xqkqfZ8Wek+rCOeRxsjMS9OgvzebEzzLYw7NHYuvzb7IdxOkI0ZHGgeEBX4PUM7QGVvNSK60T3OvWj3YfBRg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/docker-modem": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@types/docker-modem/-/docker-modem-3.0.6.tgz", @@ -4369,6 +4404,7 @@ "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", @@ -4649,6 +4685,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/swagger-ui-express": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/swagger-ui-express/-/swagger-ui-express-4.1.8.tgz", + "integrity": "sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/serve-static": "*" + } + }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", diff --git a/server/package.json b/server/package.json index 2cbacea39..2421aeb1c 100755 --- a/server/package.json +++ b/server/package.json @@ -57,6 +57,9 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", + "@types/compression": "1.8.1", + "@types/cookie-parser": "1.4.10", + "@types/cors": "2.8.19", "@types/dockerode": "^4.0.0", "@types/express": "5.0.3", "@types/gamedig": "^5.0.3", @@ -69,6 +72,7 @@ "@types/nodemailer": "7.0.1", "@types/papaparse": "^5.5.2", "@types/ping": "0.4.4", + "@types/swagger-ui-express": "4.1.8", "c8": "10.1.3", "eslint": "^9.17.0", "eslint-plugin-mocha": "^10.5.0", diff --git a/server/src/app.js b/server/src/app.ts similarity index 69% rename from server/src/app.js rename to server/src/app.ts index c83726a49..04699964d 100644 --- a/server/src/app.js +++ b/server/src/app.ts @@ -5,12 +5,27 @@ import helmet from "helmet"; import compression from "compression"; import cookieParser from "cookie-parser"; import swaggerUi from "swagger-ui-express"; -import { handleErrors } from "./middleware/handleErrors.js"; -import { setupRoutes } from "./config/routes.js"; -import { generalApiLimiter } from "./middleware/rateLimiter.js"; -import { sanitizeBody, sanitizeQuery } from "./middleware/sanitization.js"; +import { handleErrors } from "@/middleware/handleErrors.js"; +import { generalApiLimiter } from "@/middleware/rateLimiter.js"; +import { sanitizeBody, sanitizeQuery } from "@/middleware/sanitization.js"; +import { setupRoutes } from "@/config/routes.js"; +import { InitializedServices } from "@/config/services.js"; +import { InitializedControllers } from "@/config/controllers.js"; +import { EnvConfig } from "@/service/system/settingsService.js"; -export const createApp = ({ services, controllers, envSettings, frontendPath, openApiSpec }) => { +export const createApp = ({ + services, + controllers, + envSettings, + frontendPath, + openApiSpec, +}: { + services: InitializedServices; + controllers: InitializedControllers; + envSettings: EnvConfig; + frontendPath: string; + openApiSpec: any; +}) => { const allowedOrigin = envSettings.clientHost; const app = express(); app.use(generalApiLimiter); diff --git a/server/src/config/controllers.ts b/server/src/config/controllers.ts index 5ddbf2a06..d72e4f76f 100644 --- a/server/src/config/controllers.ts +++ b/server/src/config/controllers.ts @@ -10,26 +10,35 @@ import StatusPageController from "../controllers/statusPageController.js"; import NotificationController from "../controllers/notificationController.js"; import DiagnosticController from "../controllers/diagnosticController.js"; import IncidentController from "../controllers/incidentController.js"; -import type { InitializedSerivces } from "@/config/services.js"; -export const initializeControllers = (services: InitializedSerivces) => { - const controllers: Record = {}; +import type { InitializedServices } from "@/config/services.js"; - controllers.authController = new AuthController(services.userService); - - controllers.monitorController = new MonitorController(services.monitorService); - - controllers.settingsController = new SettingsController(services.settingsService, services.emailService, services.db); - controllers.checkController = new CheckController(services.checkService); - controllers.inviteController = new InviteController(services.inviteService); - - controllers.maintenanceWindowController = new MaintenanceWindowController(services.maintenanceWindowService); - controllers.queueController = new QueueController(services.jobQueue); - controllers.logController = new LogController(services.logger); - controllers.statusPageController = new StatusPageController(services.db); - controllers.notificationController = new NotificationController(services.notificationsService, services.db, services.monitorsRepository); - controllers.diagnosticController = new DiagnosticController(services.diagnosticService); - - controllers.incidentController = new IncidentController(services.incidentService); - - return controllers; +export interface InitializedControllers { + authController: AuthController; + monitorController: MonitorController; + settingsController: SettingsController; + checkController: CheckController; + inviteController: InviteController; + maintenanceWindowController: MaintenanceWindowController; + queueController: QueueController; + logController: LogController; + statusPageController: StatusPageController; + notificationController: NotificationController; + diagnosticController: DiagnosticController; + incidentController: IncidentController; +} +export const initializeControllers = (services: InitializedServices): InitializedControllers => { + return { + authController: new AuthController(services.userService), + monitorController: new MonitorController(services.monitorService), + settingsController: new SettingsController(services.settingsService, services.emailService, services.db), + checkController: new CheckController(services.checkService), + inviteController: new InviteController(services.inviteService), + maintenanceWindowController: new MaintenanceWindowController(services.maintenanceWindowService), + queueController: new QueueController(services.jobQueue), + logController: new LogController(services.logger), + statusPageController: new StatusPageController(services.db), + notificationController: new NotificationController(services.notificationsService, services.db, services.monitorsRepository), + diagnosticController: new DiagnosticController(services.diagnosticService), + incidentController: new IncidentController(services.incidentService), + }; }; diff --git a/server/src/config/index.ts b/server/src/config/index.ts deleted file mode 100644 index 7ace6f99c..000000000 --- a/server/src/config/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -import dotenv from "dotenv"; - -export interface IEevConfig { - NODE_ENV: string; - JWT_SECRET: string; - PORT: number; - PAGESPEED_API_KEY: string; - SMTP_HOST: string; - SMTP_PORT: number; - SMTP_USER: string; - SMTP_PASS: string; -} - -dotenv.config(); - -export const config: IEevConfig = { - NODE_ENV: process.env.NODE_ENV || "development", - JWT_SECRET: process.env.JWT_SECRET || "your_jwt_secret", - PORT: process.env.PORT ? parseInt(process.env.PORT, 10) : 3000, - PAGESPEED_API_KEY: process.env.PAGESPEED_API_KEY || "", - SMTP_HOST: process.env.SMTP_HOST || "smtp.example.com", - SMTP_PORT: process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT, 10) : 587, - SMTP_USER: process.env.SMTP_USER || "user@example.com", - SMTP_PASS: process.env.SMTP_PASS || "your_smtp_password", -}; diff --git a/server/src/config/services.ts b/server/src/config/services.ts index 423e23ca5..0cf55590b 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -92,8 +92,10 @@ import { INotificationsRepository, IIncidentsRepository, } from "@/repositories/index.js"; +import { ILogger } from "@/utils/logger.js"; +import { EnvConfig } from "@/service/system/settingsService.js"; -export type InitializedSerivces = { +export type InitializedServices = { //v1 settingsService: any; translationService: any; @@ -132,10 +134,10 @@ export const initializeServices = async ({ envSettings, settingsService, }: { - logger: any; - envSettings: any; + logger: ILogger; + envSettings: EnvConfig; settingsService: any; -}): Promise => { +}): Promise => { const translationService = new TranslationService(logger); await translationService.initialize(); @@ -205,7 +207,7 @@ export const initializeServices = async ({ checksRepository, }); - const bufferService = new BufferService({ logger, checkService }); + const bufferService = new BufferService({ logger, checkService, settingsService }); const statusService = new StatusService({ db, logger, buffer: bufferService, monitorsRepository }); diff --git a/server/src/index.js b/server/src/index.ts similarity index 92% rename from server/src/index.js rename to server/src/index.ts index 28dc5e8fd..b74404228 100755 --- a/server/src/index.js +++ b/server/src/index.ts @@ -7,12 +7,12 @@ import path from "path"; import fs from "fs"; import { runMigrations } from "./db/migration/index.js"; -import Logger from "./utils/logger.js"; -import SettingsService from "./service/system/settingsService.js"; +import Logger, { ILogger } from "@/utils/logger.js"; +import SettingsService from "@/service/system/settingsService.js"; import AppSettings from "./db/models/AppSettings.js"; const SERVICE_NAME = "Server"; -let logger; +let logger: ILogger; const startApp = async () => { // FE path diff --git a/server/src/service/infrastructure/bufferService.ts b/server/src/service/infrastructure/bufferService.ts index 89cd6a55f..461001800 100755 --- a/server/src/service/infrastructure/bufferService.ts +++ b/server/src/service/infrastructure/bufferService.ts @@ -1,4 +1,3 @@ -import { config } from "@/config/index.js"; import type { Check } from "@/types/index.js"; const SERVICE_NAME = "BufferService"; @@ -12,8 +11,9 @@ class BufferService { private bufferTimer: NodeJS.Timeout | null = null; private checksService: any; - constructor({ logger, checkService }: { logger: any; checkService: any }) { - this.BUFFER_TIMEOUT = config.NODE_ENV === "development" ? 10 : 1000 * 60 * 1; // 1 minute + constructor({ logger, checkService, settingsService }: { logger: any; checkService: any; settingsService: any }) { + this.BUFFER_TIMEOUT = settingsService.getSettings().nodeEnv === "development" ? 10 : 1000 * 60 * 1; // 1 minute + console.log(this.BUFFER_TIMEOUT); this.logger = logger; this.checksService = checkService; this.SERVICE_NAME = SERVICE_NAME; diff --git a/server/src/service/system/settingsService.js b/server/src/service/system/settingsService.ts similarity index 65% rename from server/src/service/system/settingsService.js rename to server/src/service/system/settingsService.ts index cea46f7ed..15f86995d 100755 --- a/server/src/service/system/settingsService.js +++ b/server/src/service/system/settingsService.ts @@ -1,6 +1,17 @@ const SERVICE_NAME = "SettingsService"; -const envConfig = { +export type EnvConfig = { + jwtSecret: string | undefined; + jwtTTL: string | undefined; + systemEmailHost: string | undefined; + nodeEnv: string | undefined; + logLevel: string | undefined; + clientHost: string | undefined; + dbConnectionString: string | undefined; + port: string | undefined; +}; + +const envConfig: EnvConfig = { jwtSecret: process.env.JWT_SECRET, jwtTTL: process.env.TOKEN_TTL, systemEmailHost: process.env.SYSTEM_EMAIL_HOST, @@ -10,19 +21,20 @@ const envConfig = { dbConnectionString: process.env.DB_CONNECTION_STRING, port: process.env.PORT, }; -/** - * SettingsService - * - * This service is responsible for loading and managing the application settings. - */ -class SettingsService { - static SERVICE_NAME = "SettingsService"; - /** - * Constructs a new SettingsService - * @constructor - * @throws {Error} - */ constructor(AppSettings) { +export interface ISettingsService { + readonly serviceName: string; + loadSettings(): EnvConfig; + getSettings(): EnvConfig; + getDBSettings(): Promise>; +} + +class SettingsService implements ISettingsService { + static SERVICE_NAME = "SettingsService"; + private AppSettings: any; + private settings: EnvConfig; + + constructor(AppSettings: any) { this.AppSettings = AppSettings; this.settings = { ...envConfig }; } @@ -31,19 +43,10 @@ class SettingsService { return SettingsService.SERVICE_NAME; } - /** - * Load settings from env settings - * @returns {Object>} The settings. - */ loadSettings() { return this.settings; } - /** - * Get the current settings. - * @returns {Object} The current settings. - * @throws Will throw an error if settings have not been loaded. - */ getSettings() { if (!this.settings) { throw new Error("Settings have not been loaded"); diff --git a/server/src/utils/logger.ts b/server/src/utils/logger.ts index ac1cbbf7c..4053f4a82 100755 --- a/server/src/utils/logger.ts +++ b/server/src/utils/logger.ts @@ -1,6 +1,7 @@ import { createLogger, format, transports, Logger as WinstonLogger } from "winston"; import type { Logform } from "winston"; import dotenv from "dotenv"; +import { EnvConfig } from "@/service/system/settingsService.js"; dotenv.config(); const SERVICE_NAME = "Logger"; @@ -18,19 +19,26 @@ interface LogEntry extends LogConfig { timestamp: string; } -interface EnvSettings { - logLevel?: string; +export interface ILogger { + readonly serviceName: string; + info(config: LogConfig): void; + warn(config: LogConfig): void; + error(config: LogConfig): void; + debug(config: LogConfig): void; + cacheLog(entry: LogEntry): void; + getLogs(): LogEntry[]; + buildLogEntry(level: string, config: LogConfig): LogEntry; } -class Logger { +class Logger implements ILogger { static SERVICE_NAME = SERVICE_NAME; private logger: WinstonLogger; - private envSettings: EnvSettings; + private envSettings: Partial; private logCache: LogEntry[]; private maxCacheSize: number; - constructor({ envSettings }: { envSettings: EnvSettings }) { + constructor({ envSettings }: { envSettings: Partial }) { this.envSettings = envSettings; this.logCache = []; this.maxCacheSize = 1000; From 371e8cc154c8bf666755331a68661be65877c797 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 21 Jan 2026 11:20:41 -0800 Subject: [PATCH 10/13] purge translationService --- server/src/config/services.ts | 7 -- .../src/service/system/translationService.js | 92 ------------------- 2 files changed, 99 deletions(-) delete mode 100755 server/src/service/system/translationService.js diff --git a/server/src/config/services.ts b/server/src/config/services.ts index 0cf55590b..b48738866 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -1,4 +1,3 @@ -import TranslationService from "../service/system/translationService.js"; import MongoDB from "../db/MongoDB.js"; import NetworkService from "../service/infrastructure/networkService.js"; import EmailService from "../service/infrastructure/emailService.js"; @@ -96,9 +95,7 @@ import { ILogger } from "@/utils/logger.js"; import { EnvConfig } from "@/service/system/settingsService.js"; export type InitializedServices = { - //v1 settingsService: any; - translationService: any; db: any; networkService: any; emailService: any; @@ -138,9 +135,6 @@ export const initializeServices = async ({ envSettings: EnvConfig; settingsService: any; }): Promise => { - const translationService = new TranslationService(logger); - await translationService.initialize(); - // Create DB const inviteModule = new InviteModule({ InviteToken, crypto }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, AppSettings }); @@ -289,7 +283,6 @@ export const initializeServices = async ({ const services = { //v1 settingsService, - translationService, db, networkService, emailService, diff --git a/server/src/service/system/translationService.js b/server/src/service/system/translationService.js deleted file mode 100755 index 0d3893852..000000000 --- a/server/src/service/system/translationService.js +++ /dev/null @@ -1,92 +0,0 @@ -import fs from "fs"; -import path from "path"; - -class TranslationService { - static SERVICE_NAME = "TranslationService"; - constructor(logger) { - this.logger = logger; - this.translations = {}; - this._language = "en"; - this.localesDir = path.join(process.cwd(), "src/locales"); - } - - get serviceName() { - return TranslationService.SERVICE_NAME; - } - - setLanguage(language) { - this._language = language; - } - - get language() { - return this._language; - } - - async initialize() { - try { - await this.loadFromFiles(); - } catch (error) { - this.logger.error({ - message: error.message, - service: "TranslationService", - method: "initialize", - stack: error.stack, - }); - } - } - - async loadFromFiles() { - try { - if (!fs.existsSync(this.localesDir)) { - return false; - } - - const files = fs.readdirSync(this.localesDir).filter((file) => file.endsWith(".json")); - - if (files.length === 0) { - return false; - } - - for (const file of files) { - const language = file.replace(".json", ""); - const filePath = path.join(this.localesDir, file); - const content = fs.readFileSync(filePath, "utf8"); - this.translations[language] = JSON.parse(content); - } - - this.logger.info({ - message: "Translations loaded from files successfully", - service: "TranslationService", - method: "loadFromFiles", - }); - - return true; - } catch (error) { - this.logger.error({ - message: error.message, - service: "TranslationService", - method: "loadFromFiles", - stack: error.stack, - }); - return false; - } - } - - getTranslation(key) { - let language = this._language; - - try { - return this.translations[language]?.[key] || this.translations["en"]?.[key] || key; - } catch (error) { - this.logger.error({ - message: error.message, - service: "TranslationService", - method: "getTranslation", - stack: error.stack, - }); - return key; - } - } -} - -export default TranslationService; From 9fa0113f2b557e2403ca411aba8516150dcc09a5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 21 Jan 2026 11:21:00 -0800 Subject: [PATCH 11/13] remove database.js --- server/src/config/database.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 server/src/config/database.js diff --git a/server/src/config/database.js b/server/src/config/database.js deleted file mode 100644 index e69de29bb..000000000 From 36e02e0a9a09ea2f851465fc64eec5014ceb540e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 21 Jan 2026 11:46:49 -0800 Subject: [PATCH 12/13] team -> ts --- server/src/config/services.ts | 7 ++++ server/src/db/models/Team.js | 14 -------- server/src/db/models/Team.ts | 27 ++++++++++++++ server/src/db/models/index.ts | 3 ++ server/src/repositories/index.ts | 3 ++ .../repositories/teams/ITeamsRepository.ts | 9 +++++ .../teams/MongoTeamsRepository.ts | 36 +++++++++++++++++++ server/src/service/business/userService.ts | 25 +++++++++---- server/src/types/index.ts | 1 + server/src/types/team.ts | 6 ++++ 10 files changed, 110 insertions(+), 21 deletions(-) delete mode 100755 server/src/db/models/Team.js create mode 100755 server/src/db/models/Team.ts create mode 100644 server/src/repositories/teams/ITeamsRepository.ts create mode 100644 server/src/repositories/teams/MongoTeamsRepository.ts create mode 100644 server/src/types/team.ts diff --git a/server/src/config/services.ts b/server/src/config/services.ts index b48738866..8cfcb400b 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -80,6 +80,7 @@ import { MongoSettingsRepository, MongoNotificationsRepository, MongoIncidentRepository, + MongoTeamsRepository, IMonitorsRepository, IChecksRepository, IMonitorStatsRepository, @@ -90,6 +91,7 @@ import { ISettingsRepository, INotificationsRepository, IIncidentsRepository, + ITeamsRepository, } from "@/repositories/index.js"; import { ILogger } from "@/utils/logger.js"; import { EnvConfig } from "@/service/system/settingsService.js"; @@ -124,6 +126,7 @@ export type InitializedServices = { settingsRepository: ISettingsRepository; notificationsRepository: INotificationsRepository; incidentsRepository: IIncidentsRepository; + teamsRepository: ITeamsRepository; }; export const initializeServices = async ({ @@ -171,6 +174,8 @@ export const initializeServices = async ({ const settingsRepository = new MongoSettingsRepository(); const notificationsRepository = new MongoNotificationsRepository(); const incidentsRepository = new MongoIncidentRepository(); + const teamsRepository = new MongoTeamsRepository(); + const networkService = new NetworkService({ axios, got, @@ -254,6 +259,7 @@ export const initializeServices = async ({ invitesRepository, recoveryTokensRepository, settingsRepository, + teamsRepository, }); const diagnosticService = new DiagnosticService(); @@ -311,6 +317,7 @@ export const initializeServices = async ({ settingsRepository, notificationsRepository, incidentsRepository, + teamsRepository, }; return services; diff --git a/server/src/db/models/Team.js b/server/src/db/models/Team.js deleted file mode 100755 index 95337c201..000000000 --- a/server/src/db/models/Team.js +++ /dev/null @@ -1,14 +0,0 @@ -import mongoose from "mongoose"; -const TeamSchema = mongoose.Schema( - { - email: { - type: String, - required: true, - unique: true, - }, - }, - { - timestamps: true, - } -); -export default mongoose.model("Team", TeamSchema); diff --git a/server/src/db/models/Team.ts b/server/src/db/models/Team.ts new file mode 100755 index 000000000..1b177a108 --- /dev/null +++ b/server/src/db/models/Team.ts @@ -0,0 +1,27 @@ +import { Schema, model, type Types } from "mongoose"; + +import type { Team } from "@/types/index.js"; + +interface TeamDocument extends Team { + _id: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const TeamSchema = new Schema( + { + email: { + type: String, + required: true, + unique: true, + }, + }, + { + timestamps: true, + } +); +const TeamModel = model("Team", TeamSchema); + +export type { TeamDocument }; +export { TeamModel }; +export default TeamModel; diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts index 4b71937bb..f762027ec 100644 --- a/server/src/db/models/index.ts +++ b/server/src/db/models/index.ts @@ -27,3 +27,6 @@ export { default as NotificationModel } from "@/db/models/Notification.js"; export * from "@/db/models/Incident.js"; export { default as IncidentModel } from "@/db/models/Incident.js"; + +export * from "@/db/models/Team.js"; +export { default as TeamModel } from "@/db/models/Team.js"; diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index 72dfd2f40..ac5125af9 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -27,3 +27,6 @@ export { default as MongoNotificationsRepository } from "@/repositories/notifica export * from "@/repositories/incidents/IIncidentsRepository.js"; export { default as MongoIncidentRepository } from "@/repositories/incidents/MongoIncidentRepository.js"; + +export * from "@/repositories/teams/ITeamsRepository.js"; +export { default as MongoTeamsRepository } from "@/repositories/teams/MongoTeamsRepository.js"; diff --git a/server/src/repositories/teams/ITeamsRepository.ts b/server/src/repositories/teams/ITeamsRepository.ts new file mode 100644 index 000000000..5ba4b36dd --- /dev/null +++ b/server/src/repositories/teams/ITeamsRepository.ts @@ -0,0 +1,9 @@ +import type { Team } from "@/types/index.js"; +export interface ITeamRepository { + // create + create(email: string): Promise; + // fetch + // update + // delete + // other +} diff --git a/server/src/repositories/teams/MongoTeamsRepository.ts b/server/src/repositories/teams/MongoTeamsRepository.ts new file mode 100644 index 000000000..1a2d8ae57 --- /dev/null +++ b/server/src/repositories/teams/MongoTeamsRepository.ts @@ -0,0 +1,36 @@ +import { Team } from "@/types/index.js"; +import { TeamDocument, TeamModel } from "@/db/models/index.js"; +import { ITeamRepository } from "@/repositories/index.js"; +import mongoose from "mongoose"; + +class MongoTeamRepository implements ITeamRepository { + private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => { + if (!value) { + return ""; + } + return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value); + }; + + private toDateString = (value?: Date | string | null): string => { + if (!value) { + return new Date(0).toISOString(); + } + return value instanceof Date ? value.toISOString() : new Date(value).toISOString(); + }; + + private toEntity = (doc: TeamDocument): Team => { + return { + id: this.toStringId(doc._id), + email: doc.email, + createdAt: this.toDateString(doc.createdAt), + updatedAt: this.toDateString(doc.updatedAt), + }; + }; + + create = async (email: string) => { + const team = await TeamModel.create({ email }); + return this.toEntity(team); + }; +} + +export default MongoTeamRepository; diff --git a/server/src/service/business/userService.ts b/server/src/service/business/userService.ts index f6f489b8e..2c31cff9e 100644 --- a/server/src/service/business/userService.ts +++ b/server/src/service/business/userService.ts @@ -1,9 +1,15 @@ -import { IInvitesRepository, IMonitorsRepository, IRecoveryTokensRepository, IUsersRepository, ISettingsRepository } from "@/repositories/index.js"; -import Team from "@/db/models/Team.js"; +import { + IInvitesRepository, + IMonitorsRepository, + IRecoveryTokensRepository, + IUsersRepository, + ISettingsRepository, + ITeamRepository, +} from "@/repositories/index.js"; import type { User } from "@/types/index.js"; import bcrypt from "bcryptjs"; import { AppError } from "@/utils/AppError.js"; -import { ISuperSimpleQueue } from "../infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; +import { ISuperSimpleQueue } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; const SERVICE_NAME = "userService"; @@ -21,6 +27,7 @@ class UserService { private invitesRepository: IInvitesRepository; private recoveryTokensRepository: IRecoveryTokensRepository; private settingsRepository: ISettingsRepository; + private teamsRepository: ITeamRepository; constructor({ crypto, @@ -34,6 +41,7 @@ class UserService { invitesRepository, recoveryTokensRepository, settingsRepository, + teamsRepository, }: { crypto: any; emailService: any; @@ -47,6 +55,7 @@ class UserService { invitesRepository: IInvitesRepository; recoveryTokensRepository: IRecoveryTokensRepository; settingsRepository: ISettingsRepository; + teamsRepository: ITeamRepository; }) { this.emailService = emailService; this.settingsService = settingsService; @@ -59,6 +68,7 @@ class UserService { this.invitesRepository = invitesRepository; this.recoveryTokensRepository = recoveryTokensRepository; this.settingsRepository = settingsRepository; + this.teamsRepository = teamsRepository; } get serviceName() { @@ -85,10 +95,11 @@ class UserService { const jwtSecret = this.crypto.randomBytes(64).toString("hex"); await this.settingsRepository.update({ jwtSecret }); // Create a new team - const team = new Team({ - email: user.email, - }); - user.teamId = team._id; + if (!user.email) { + throw new AppError({ message: "Email is required for first user", service: SERVICE_NAME, method: "registerUser", status: 400 }); + } + const team = await this.teamsRepository.create(user.email); + user.teamId = team.id; } const newUser = await this.usersRepository.create({ ...user }, file); diff --git a/server/src/types/index.ts b/server/src/types/index.ts index b0b6b07c5..9464622ac 100644 --- a/server/src/types/index.ts +++ b/server/src/types/index.ts @@ -11,3 +11,4 @@ export * from "@/types/notification.js"; export * from "@/types/alert.js"; export * from "@/types/incident.js"; export * from "@/types/email.js"; +export * from "@/types/team.js"; diff --git a/server/src/types/team.ts b/server/src/types/team.ts new file mode 100644 index 000000000..1bca943a0 --- /dev/null +++ b/server/src/types/team.ts @@ -0,0 +1,6 @@ +export interface Team { + id: string; + email: string; + createdAt: string; + updatedAt: string; +} From 5b96075c0358854e1849b3e5b97a6b993696fe82 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 21 Jan 2026 11:56:05 -0800 Subject: [PATCH 13/13] build errs --- server/src/db/models/Team.ts | 3 ++- server/src/repositories/teams/ITeamsRepository.ts | 2 +- server/src/repositories/teams/MongoTeamsRepository.ts | 6 +++--- server/src/service/business/userService.ts | 6 +++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/server/src/db/models/Team.ts b/server/src/db/models/Team.ts index 1b177a108..608f90ad8 100755 --- a/server/src/db/models/Team.ts +++ b/server/src/db/models/Team.ts @@ -2,7 +2,8 @@ import { Schema, model, type Types } from "mongoose"; import type { Team } from "@/types/index.js"; -interface TeamDocument extends Team { +type TeamDocumentBase = Omit & {}; +interface TeamDocument extends TeamDocumentBase { _id: Types.ObjectId; createdAt: Date; updatedAt: Date; diff --git a/server/src/repositories/teams/ITeamsRepository.ts b/server/src/repositories/teams/ITeamsRepository.ts index 5ba4b36dd..9ccdd97fb 100644 --- a/server/src/repositories/teams/ITeamsRepository.ts +++ b/server/src/repositories/teams/ITeamsRepository.ts @@ -1,5 +1,5 @@ import type { Team } from "@/types/index.js"; -export interface ITeamRepository { +export interface ITeamsRepository { // create create(email: string): Promise; // fetch diff --git a/server/src/repositories/teams/MongoTeamsRepository.ts b/server/src/repositories/teams/MongoTeamsRepository.ts index 1a2d8ae57..93a306787 100644 --- a/server/src/repositories/teams/MongoTeamsRepository.ts +++ b/server/src/repositories/teams/MongoTeamsRepository.ts @@ -1,9 +1,9 @@ import { Team } from "@/types/index.js"; import { TeamDocument, TeamModel } from "@/db/models/index.js"; -import { ITeamRepository } from "@/repositories/index.js"; +import { ITeamsRepository } from "@/repositories/index.js"; import mongoose from "mongoose"; -class MongoTeamRepository implements ITeamRepository { +class MongoTeamsRepository implements ITeamsRepository { private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => { if (!value) { return ""; @@ -33,4 +33,4 @@ class MongoTeamRepository implements ITeamRepository { }; } -export default MongoTeamRepository; +export default MongoTeamsRepository; diff --git a/server/src/service/business/userService.ts b/server/src/service/business/userService.ts index 2c31cff9e..b9883f594 100644 --- a/server/src/service/business/userService.ts +++ b/server/src/service/business/userService.ts @@ -4,7 +4,7 @@ import { IRecoveryTokensRepository, IUsersRepository, ISettingsRepository, - ITeamRepository, + ITeamsRepository, } from "@/repositories/index.js"; import type { User } from "@/types/index.js"; import bcrypt from "bcryptjs"; @@ -27,7 +27,7 @@ class UserService { private invitesRepository: IInvitesRepository; private recoveryTokensRepository: IRecoveryTokensRepository; private settingsRepository: ISettingsRepository; - private teamsRepository: ITeamRepository; + private teamsRepository: ITeamsRepository; constructor({ crypto, @@ -55,7 +55,7 @@ class UserService { invitesRepository: IInvitesRepository; recoveryTokensRepository: IRecoveryTokensRepository; settingsRepository: ISettingsRepository; - teamsRepository: ITeamRepository; + teamsRepository: ITeamsRepository; }) { this.emailService = emailService; this.settingsService = settingsService;