From aa61e0f321059c1eaba2799819fd544b88e19d4c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 12 Mar 2026 09:13:35 -0700 Subject: [PATCH 1/6] refacotr settingsController --- server/src/controllers/settingsController.ts | 14 +++++---- server/src/service/system/settingsService.ts | 1 + server/src/validation/settingsValidation.ts | 30 ++++++++++---------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/server/src/controllers/settingsController.ts b/server/src/controllers/settingsController.ts index d3caa0d8f..0fd82a8ac 100644 --- a/server/src/controllers/settingsController.ts +++ b/server/src/controllers/settingsController.ts @@ -2,14 +2,15 @@ import { Request, Response, NextFunction } from "express"; import { updateAppSettingsBodyValidation } from "@/validation/settingsValidation.js"; import { sendTestEmailBodyValidation } from "@/validation/notificationValidation.js"; import { AppError } from "@/utils/AppError.js"; +import { IEmailService, ISettingsService } from "@/service/index.js"; const SERVICE_NAME = "SettingsController"; class SettingsController { static SERVICE_NAME = SERVICE_NAME; - private settingsService: any; - private emailService: any; - constructor(settingsService: any, emailService: any) { + private settingsService: ISettingsService; + private emailService: IEmailService; + constructor(settingsService: ISettingsService, emailService: IEmailService) { this.settingsService = settingsService; this.emailService = emailService; } @@ -57,9 +58,9 @@ class SettingsController { updateAppSettings = async (req: Request, res: Response, next: NextFunction) => { try { - updateAppSettingsBodyValidation.parse(req.body); + const validatedBody = updateAppSettingsBodyValidation.parse(req.body); - const updatedSettings = await this.settingsService.updateDbSettings(req.body); + const updatedSettings = await this.settingsService.updateDbSettings(validatedBody); const returnSettings = this.buildAppSettings(updatedSettings); return res.status(200).json({ success: true, @@ -95,6 +96,9 @@ class SettingsController { const context = { testName: "Monitoring System" }; const html = await this.emailService.buildEmail("testEmailTemplate", context); + if (!html) { + throw new AppError({ message: "Failed to build email template.", status: 500 }); + } const messageId = await this.emailService.sendEmail(to, subject, html, { systemEmailHost, systemEmailPort, diff --git a/server/src/service/system/settingsService.ts b/server/src/service/system/settingsService.ts index ac4dfc425..10ac61725 100755 --- a/server/src/service/system/settingsService.ts +++ b/server/src/service/system/settingsService.ts @@ -19,6 +19,7 @@ export interface ISettingsService { loadSettings(): EnvConfig; getSettings(): EnvConfig; getDBSettings(): Promise; + updateDbSettings(newSettings: Partial): Promise; } export class SettingsService implements ISettingsService { diff --git a/server/src/validation/settingsValidation.ts b/server/src/validation/settingsValidation.ts index e7f0dc9b4..c89928155 100644 --- a/server/src/validation/settingsValidation.ts +++ b/server/src/validation/settingsValidation.ts @@ -7,17 +7,17 @@ import { z } from "zod"; export const updateAppSettingsBodyValidation = z .object({ - checkTTL: z.union([z.number().int().min(1).max(CHECK_TTL_SENTINEL), z.literal("")]).optional(), - systemEmailPort: z.union([z.number(), z.literal("")]).optional(), - pagespeedApiKey: z.union([z.string(), z.literal("")]).optional(), - language: z.union([z.string(), z.literal("")]).optional(), - timezone: z.union([z.string(), z.literal("")]).optional(), - systemEmailHost: z.union([z.string(), z.literal("")]).optional(), - systemEmailAddress: z.union([z.string(), z.literal("")]).optional(), - systemEmailPassword: z.union([z.string(), z.literal("")]).optional(), - systemEmailUser: z.union([z.string(), z.literal("")]).optional(), - systemEmailConnectionHost: z.union([z.string(), z.literal("")]).optional(), - systemEmailTLSServername: z.union([z.string(), z.literal("")]).optional(), + checkTTL: z.union([z.number().int().min(1).max(CHECK_TTL_SENTINEL)]).optional(), + systemEmailPort: z.number().optional(), + pagespeedApiKey: z.string().optional(), + language: z.string().optional(), + timezone: z.string().optional(), + systemEmailHost: z.string().optional(), + systemEmailAddress: z.string().optional(), + systemEmailPassword: z.string().optional(), + systemEmailUser: z.string().optional(), + systemEmailConnectionHost: z.string().optional(), + systemEmailTLSServername: z.string().optional(), showURL: z.boolean().optional(), systemEmailSecure: z.boolean().optional(), @@ -28,10 +28,10 @@ export const updateAppSettingsBodyValidation = z globalThresholds: z .object({ - cpu: z.union([z.number().min(1).max(100), z.literal("")]).optional(), - memory: z.union([z.number().min(1).max(100), z.literal("")]).optional(), - disk: z.union([z.number().min(1).max(100), z.literal("")]).optional(), - temperature: z.union([z.number().min(1).max(150), z.literal("")]).optional(), + cpu: z.union([z.number().min(1).max(100)]).optional(), + memory: z.union([z.number().min(1).max(100)]).optional(), + disk: z.union([z.number().min(1).max(100)]).optional(), + temperature: z.union([z.number().min(1).max(150)]).optional(), }) .optional(), }) From d070a33d7ffbf312d5217430dece8312d405a6f5 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 12 Mar 2026 09:32:34 -0700 Subject: [PATCH 2/6] remove dead union, remove unused undefined -> conversion --- server/src/validation/settingsValidation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/validation/settingsValidation.ts b/server/src/validation/settingsValidation.ts index c89928155..91953eef4 100644 --- a/server/src/validation/settingsValidation.ts +++ b/server/src/validation/settingsValidation.ts @@ -7,7 +7,7 @@ import { z } from "zod"; export const updateAppSettingsBodyValidation = z .object({ - checkTTL: z.union([z.number().int().min(1).max(CHECK_TTL_SENTINEL)]).optional(), + checkTTL: z.number().int().min(1).max(CHECK_TTL_SENTINEL).optional(), systemEmailPort: z.number().optional(), pagespeedApiKey: z.string().optional(), language: z.string().optional(), From 8ec285ed41bdce169431d2f1c8fa7de9c9564fe2 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 12 Mar 2026 09:35:33 -0700 Subject: [PATCH 3/6] remove dead unions --- client/src/Pages/Settings/index.tsx | 10 +--------- server/src/validation/settingsValidation.ts | 8 ++++---- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/client/src/Pages/Settings/index.tsx b/client/src/Pages/Settings/index.tsx index 679d66126..e51255157 100644 --- a/client/src/Pages/Settings/index.tsx +++ b/client/src/Pages/Settings/index.tsx @@ -262,15 +262,7 @@ export const SettingsPage = () => { if (value === undefined) { (acc as any)[typedKey] = ""; } else if (typeof value === "object" && value !== null) { - // Handle nested objects like globalThresholds - // Convert 0 to "" for unsetting thresholds - (acc as any)[typedKey] = Object.entries(value).reduce( - (nested, [nestedKey, nestedValue]) => ({ - ...nested, - [nestedKey]: nestedValue === undefined ? "" : nestedValue, - }), - {} - ); + (acc as any)[typedKey] = value; } else { (acc as any)[typedKey] = value; } diff --git a/server/src/validation/settingsValidation.ts b/server/src/validation/settingsValidation.ts index 91953eef4..b9551dd4e 100644 --- a/server/src/validation/settingsValidation.ts +++ b/server/src/validation/settingsValidation.ts @@ -28,10 +28,10 @@ export const updateAppSettingsBodyValidation = z globalThresholds: z .object({ - cpu: z.union([z.number().min(1).max(100)]).optional(), - memory: z.union([z.number().min(1).max(100)]).optional(), - disk: z.union([z.number().min(1).max(100)]).optional(), - temperature: z.union([z.number().min(1).max(150)]).optional(), + cpu: z.number().min(1).max(100).optional(), + memory: z.number().min(1).max(100).optional(), + disk: z.number().min(1).max(100).optional(), + temperature: z.number().min(1).max(150).optional(), }) .optional(), }) From 84ca5db0fa490b47f8a29c0b5d923ff7ba961ba3 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 12 Mar 2026 09:13:35 -0700 Subject: [PATCH 4/6] refactor settingsController --- server/src/controllers/settingsController.ts | 14 +++++---- server/src/service/system/settingsService.ts | 1 + server/src/validation/settingsValidation.ts | 30 ++++++++++---------- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/server/src/controllers/settingsController.ts b/server/src/controllers/settingsController.ts index d3caa0d8f..0fd82a8ac 100644 --- a/server/src/controllers/settingsController.ts +++ b/server/src/controllers/settingsController.ts @@ -2,14 +2,15 @@ import { Request, Response, NextFunction } from "express"; import { updateAppSettingsBodyValidation } from "@/validation/settingsValidation.js"; import { sendTestEmailBodyValidation } from "@/validation/notificationValidation.js"; import { AppError } from "@/utils/AppError.js"; +import { IEmailService, ISettingsService } from "@/service/index.js"; const SERVICE_NAME = "SettingsController"; class SettingsController { static SERVICE_NAME = SERVICE_NAME; - private settingsService: any; - private emailService: any; - constructor(settingsService: any, emailService: any) { + private settingsService: ISettingsService; + private emailService: IEmailService; + constructor(settingsService: ISettingsService, emailService: IEmailService) { this.settingsService = settingsService; this.emailService = emailService; } @@ -57,9 +58,9 @@ class SettingsController { updateAppSettings = async (req: Request, res: Response, next: NextFunction) => { try { - updateAppSettingsBodyValidation.parse(req.body); + const validatedBody = updateAppSettingsBodyValidation.parse(req.body); - const updatedSettings = await this.settingsService.updateDbSettings(req.body); + const updatedSettings = await this.settingsService.updateDbSettings(validatedBody); const returnSettings = this.buildAppSettings(updatedSettings); return res.status(200).json({ success: true, @@ -95,6 +96,9 @@ class SettingsController { const context = { testName: "Monitoring System" }; const html = await this.emailService.buildEmail("testEmailTemplate", context); + if (!html) { + throw new AppError({ message: "Failed to build email template.", status: 500 }); + } const messageId = await this.emailService.sendEmail(to, subject, html, { systemEmailHost, systemEmailPort, diff --git a/server/src/service/system/settingsService.ts b/server/src/service/system/settingsService.ts index ac4dfc425..10ac61725 100755 --- a/server/src/service/system/settingsService.ts +++ b/server/src/service/system/settingsService.ts @@ -19,6 +19,7 @@ export interface ISettingsService { loadSettings(): EnvConfig; getSettings(): EnvConfig; getDBSettings(): Promise; + updateDbSettings(newSettings: Partial): Promise; } export class SettingsService implements ISettingsService { diff --git a/server/src/validation/settingsValidation.ts b/server/src/validation/settingsValidation.ts index e7f0dc9b4..c89928155 100644 --- a/server/src/validation/settingsValidation.ts +++ b/server/src/validation/settingsValidation.ts @@ -7,17 +7,17 @@ import { z } from "zod"; export const updateAppSettingsBodyValidation = z .object({ - checkTTL: z.union([z.number().int().min(1).max(CHECK_TTL_SENTINEL), z.literal("")]).optional(), - systemEmailPort: z.union([z.number(), z.literal("")]).optional(), - pagespeedApiKey: z.union([z.string(), z.literal("")]).optional(), - language: z.union([z.string(), z.literal("")]).optional(), - timezone: z.union([z.string(), z.literal("")]).optional(), - systemEmailHost: z.union([z.string(), z.literal("")]).optional(), - systemEmailAddress: z.union([z.string(), z.literal("")]).optional(), - systemEmailPassword: z.union([z.string(), z.literal("")]).optional(), - systemEmailUser: z.union([z.string(), z.literal("")]).optional(), - systemEmailConnectionHost: z.union([z.string(), z.literal("")]).optional(), - systemEmailTLSServername: z.union([z.string(), z.literal("")]).optional(), + checkTTL: z.union([z.number().int().min(1).max(CHECK_TTL_SENTINEL)]).optional(), + systemEmailPort: z.number().optional(), + pagespeedApiKey: z.string().optional(), + language: z.string().optional(), + timezone: z.string().optional(), + systemEmailHost: z.string().optional(), + systemEmailAddress: z.string().optional(), + systemEmailPassword: z.string().optional(), + systemEmailUser: z.string().optional(), + systemEmailConnectionHost: z.string().optional(), + systemEmailTLSServername: z.string().optional(), showURL: z.boolean().optional(), systemEmailSecure: z.boolean().optional(), @@ -28,10 +28,10 @@ export const updateAppSettingsBodyValidation = z globalThresholds: z .object({ - cpu: z.union([z.number().min(1).max(100), z.literal("")]).optional(), - memory: z.union([z.number().min(1).max(100), z.literal("")]).optional(), - disk: z.union([z.number().min(1).max(100), z.literal("")]).optional(), - temperature: z.union([z.number().min(1).max(150), z.literal("")]).optional(), + cpu: z.union([z.number().min(1).max(100)]).optional(), + memory: z.union([z.number().min(1).max(100)]).optional(), + disk: z.union([z.number().min(1).max(100)]).optional(), + temperature: z.union([z.number().min(1).max(150)]).optional(), }) .optional(), }) From fa8764fde2822498d351a0ea0fb1f20353341a8b Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 12 Mar 2026 09:32:34 -0700 Subject: [PATCH 5/6] remove dead union, remove unused undefined -> conversion --- server/src/validation/settingsValidation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/validation/settingsValidation.ts b/server/src/validation/settingsValidation.ts index c89928155..91953eef4 100644 --- a/server/src/validation/settingsValidation.ts +++ b/server/src/validation/settingsValidation.ts @@ -7,7 +7,7 @@ import { z } from "zod"; export const updateAppSettingsBodyValidation = z .object({ - checkTTL: z.union([z.number().int().min(1).max(CHECK_TTL_SENTINEL)]).optional(), + checkTTL: z.number().int().min(1).max(CHECK_TTL_SENTINEL).optional(), systemEmailPort: z.number().optional(), pagespeedApiKey: z.string().optional(), language: z.string().optional(), From e9cb302d6e2d5ecb6196e99da1c8814d64c9bfbf Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 12 Mar 2026 09:35:33 -0700 Subject: [PATCH 6/6] remove dead unions --- client/src/Pages/Settings/index.tsx | 10 +--------- server/src/validation/settingsValidation.ts | 8 ++++---- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/client/src/Pages/Settings/index.tsx b/client/src/Pages/Settings/index.tsx index 679d66126..e51255157 100644 --- a/client/src/Pages/Settings/index.tsx +++ b/client/src/Pages/Settings/index.tsx @@ -262,15 +262,7 @@ export const SettingsPage = () => { if (value === undefined) { (acc as any)[typedKey] = ""; } else if (typeof value === "object" && value !== null) { - // Handle nested objects like globalThresholds - // Convert 0 to "" for unsetting thresholds - (acc as any)[typedKey] = Object.entries(value).reduce( - (nested, [nestedKey, nestedValue]) => ({ - ...nested, - [nestedKey]: nestedValue === undefined ? "" : nestedValue, - }), - {} - ); + (acc as any)[typedKey] = value; } else { (acc as any)[typedKey] = value; } diff --git a/server/src/validation/settingsValidation.ts b/server/src/validation/settingsValidation.ts index 91953eef4..b9551dd4e 100644 --- a/server/src/validation/settingsValidation.ts +++ b/server/src/validation/settingsValidation.ts @@ -28,10 +28,10 @@ export const updateAppSettingsBodyValidation = z globalThresholds: z .object({ - cpu: z.union([z.number().min(1).max(100)]).optional(), - memory: z.union([z.number().min(1).max(100)]).optional(), - disk: z.union([z.number().min(1).max(100)]).optional(), - temperature: z.union([z.number().min(1).max(150)]).optional(), + cpu: z.number().min(1).max(100).optional(), + memory: z.number().min(1).max(100).optional(), + disk: z.number().min(1).max(100).optional(), + temperature: z.number().min(1).max(150).optional(), }) .optional(), })