From 27d37ece7decd358cca955af8ab02c03bd2cb657 Mon Sep 17 00:00:00 2001 From: gorkem-bwl Date: Sun, 12 Oct 2025 21:11:20 -0400 Subject: [PATCH] feat: Add monitor grouping functionality to backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add optional 'group' field to Monitor schema with trimming and validation - Add group validation to Joi schemas for create and edit monitor endpoints - Add GET /api/v1/monitors/team/groups endpoint to fetch unique groups - Implement case-insensitive group filtering in database layer - Support for organizing monitors into collapsible groups 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- server/src/controllers/v1/monitorController.js | 18 ++++++++++++++++++ server/src/db/v1/models/Monitor.js | 9 +++++++++ server/src/db/v1/modules/monitorModule.js | 15 +++++++++++++++ server/src/routes/v1/monitorRoute.js | 1 + .../src/service/v1/business/monitorService.js | 5 +++++ server/src/validation/joi.js | 2 ++ 6 files changed, 50 insertions(+) diff --git a/server/src/controllers/v1/monitorController.js b/server/src/controllers/v1/monitorController.js index 462d5a278..5ac44fa94 100644 --- a/server/src/controllers/v1/monitorController.js +++ b/server/src/controllers/v1/monitorController.js @@ -452,6 +452,24 @@ class MonitorController extends BaseController { SERVICE_NAME, "getAllGames" ); + + getGroupsByTeamId = this.asyncHandler( + async (req, res) => { + const teamId = req?.user?.teamId; + if (!teamId) { + throw this.errorService.createBadRequestError("Team ID is required"); + } + + const groups = await this.monitorService.getGroupsByTeamId({ teamId }); + + return res.success({ + msg: "OK", + data: groups, + }); + }, + SERVICE_NAME, + "getGroupsByTeamId" + ); } export default MonitorController; diff --git a/server/src/db/v1/models/Monitor.js b/server/src/db/v1/models/Monitor.js index 0e12d3c26..7d499dcce 100755 --- a/server/src/db/v1/models/Monitor.js +++ b/server/src/db/v1/models/Monitor.js @@ -128,6 +128,15 @@ const MonitorSchema = mongoose.Schema( gameId: { type: String, }, + group: { + type: String, + trim: true, + maxLength: 50, + default: null, + set: function(value) { + return value && value.trim() ? value.trim() : null; + } + }, }, { timestamps: true, diff --git a/server/src/db/v1/modules/monitorModule.js b/server/src/db/v1/modules/monitorModule.js index f795a7f2e..83aa29dfe 100755 --- a/server/src/db/v1/modules/monitorModule.js +++ b/server/src/db/v1/modules/monitorModule.js @@ -573,6 +573,21 @@ class MonitorModule { throw error; } }; + + getGroupsByTeamId = async ({ teamId }) => { + try { + const groups = await this.Monitor.distinct("group", { + teamId: new this.ObjectId(teamId), + group: { $ne: null, $ne: "" } + }); + + return groups.filter(Boolean).sort(); + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getGroupsByTeamId"; + throw error; + } + }; } export default MonitorModule; diff --git a/server/src/routes/v1/monitorRoute.js b/server/src/routes/v1/monitorRoute.js index 107964f6b..dc0dd694b 100755 --- a/server/src/routes/v1/monitorRoute.js +++ b/server/src/routes/v1/monitorRoute.js @@ -18,6 +18,7 @@ class MonitorRoutes { this.router.get("/team", this.monitorController.getMonitorsByTeamId); this.router.get("/team/with-checks", this.monitorController.getMonitorsWithChecksByTeamId); this.router.get("/team/summary", this.monitorController.getMonitorsAndSummaryByTeamId); + this.router.get("/team/groups", this.monitorController.getGroupsByTeamId); // Uptime routes this.router.get("/uptime/details/:monitorId", this.monitorController.getUptimeDetailsById); diff --git a/server/src/service/v1/business/monitorService.js b/server/src/service/v1/business/monitorService.js index 061234102..2b643ee64 100644 --- a/server/src/service/v1/business/monitorService.js +++ b/server/src/service/v1/business/monitorService.js @@ -267,6 +267,11 @@ class MonitorService { getAllGames = () => { return this.games; }; + + getGroupsByTeamId = async ({ teamId }) => { + const groups = await this.db.monitorModule.getGroupsByTeamId({ teamId }); + return groups; + }; } export default MonitorService; diff --git a/server/src/validation/joi.js b/server/src/validation/joi.js index 1cd5cd5d6..87d2b2a8c 100755 --- a/server/src/validation/joi.js +++ b/server/src/validation/joi.js @@ -174,6 +174,7 @@ const createMonitorBodyValidation = joi.object({ expectedValue: joi.string().allow(""), matchMethod: joi.string(), gameId: joi.string().allow(""), + group: joi.string().max(50).trim().allow(null, "").optional(), }); const createMonitorsBodyValidation = joi.array().items( @@ -203,6 +204,7 @@ const editMonitorBodyValidation = joi.object({ usage_temperature: joi.number(), }), gameId: joi.string(), + group: joi.string().max(50).trim().allow(null, "").optional(), }); const pauseMonitorParamValidation = joi.object({