Merge pull request #3169 from bluewave-labs/feat/maintenance-window-repository
feat/maintenance window repository
This commit is contained in:
18 files changed
+297
-351
No files matched your search
@@ -27,7 +27,7 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
await networkService.deleteMaintenanceWindow({
|
||||
maintenanceWindowId: maintenanceWindow._id,
|
||||
maintenanceWindowId: maintenanceWindow.id,
|
||||
});
|
||||
updateCallback();
|
||||
createToast({ body: "Maintenance window deleted successfully." });
|
||||
@@ -47,7 +47,7 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
|
||||
active: !maintenanceWindow.active,
|
||||
};
|
||||
await networkService.editMaintenanceWindow({
|
||||
maintenanceWindowId: maintenanceWindow._id,
|
||||
maintenanceWindowId: maintenanceWindow.id,
|
||||
maintenanceWindow: data,
|
||||
});
|
||||
updateCallback();
|
||||
@@ -60,7 +60,7 @@ const ActionsMenu = ({ /* isAdmin, */ maintenanceWindow, updateCallback }) => {
|
||||
};
|
||||
|
||||
const handleEdit = () => {
|
||||
navigate(`/maintenance/create/${maintenanceWindow._id}`);
|
||||
navigate(`/maintenance/create/${maintenanceWindow.id}`);
|
||||
};
|
||||
|
||||
const openMenu = (event) => {
|
||||
|
||||
@@ -203,7 +203,7 @@ const MaintenanceTable = ({
|
||||
},
|
||||
},
|
||||
onRowClick: (row) => {
|
||||
navigate(`/maintenance/create/${row._id}`);
|
||||
navigate(`/maintenance/create/${row.id}`);
|
||||
},
|
||||
}}
|
||||
headers={headers}
|
||||
|
||||
@@ -60,8 +60,6 @@ import AppSettings from "../db/models/AppSettings.js";
|
||||
import Incident from "../db/models/Incident.js";
|
||||
|
||||
import StatusPageModule from "../db/modules/statusPageModule.js";
|
||||
import MaintenanceWindowModule from "../db/modules/maintenanceWindowModule.js";
|
||||
import NotificationModule from "../db/modules/notificationModule.js";
|
||||
import IncidentModule from "../db/modules/incidentModule.js";
|
||||
|
||||
// repositories
|
||||
@@ -76,6 +74,7 @@ import {
|
||||
MongoNotificationsRepository,
|
||||
MongoIncidentRepository,
|
||||
MongoTeamsRepository,
|
||||
MongoMaintenanceWindowsRepository,
|
||||
IMonitorsRepository,
|
||||
IChecksRepository,
|
||||
IMonitorStatsRepository,
|
||||
@@ -87,6 +86,7 @@ import {
|
||||
INotificationsRepository,
|
||||
IIncidentsRepository,
|
||||
ITeamsRepository,
|
||||
IMaintenanceWindowsRepository,
|
||||
} from "@/repositories/index.js";
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { EnvConfig } from "@/service/system/settingsService.js";
|
||||
@@ -122,6 +122,7 @@ export type InitializedServices = {
|
||||
notificationsRepository: INotificationsRepository;
|
||||
incidentsRepository: IIncidentsRepository;
|
||||
teamsRepository: ITeamsRepository;
|
||||
maintenanceWindowsRepository: IMaintenanceWindowsRepository;
|
||||
};
|
||||
|
||||
export const initializeServices = async ({
|
||||
@@ -137,16 +138,12 @@ export const initializeServices = async ({
|
||||
}): Promise<InitializedServices> => {
|
||||
// Create DB
|
||||
const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, AppSettings });
|
||||
const maintenanceWindowModule = new MaintenanceWindowModule({ MaintenanceWindow });
|
||||
const notificationModule = new NotificationModule({ Notification: NotificationModel, Monitor });
|
||||
const incidentModule = new IncidentModule({ logger, Incident, Monitor, User });
|
||||
|
||||
const db = new MongoDB({
|
||||
logger,
|
||||
envSettings,
|
||||
statusPageModule,
|
||||
maintenanceWindowModule,
|
||||
notificationModule,
|
||||
incidentModule,
|
||||
});
|
||||
|
||||
@@ -163,6 +160,7 @@ export const initializeServices = async ({
|
||||
const notificationsRepository = new MongoNotificationsRepository();
|
||||
const incidentsRepository = new MongoIncidentRepository();
|
||||
const teamsRepository = new MongoTeamsRepository();
|
||||
const maintenanceWindowsRepository = new MongoMaintenanceWindowsRepository();
|
||||
|
||||
const networkService = new NetworkService({
|
||||
axios,
|
||||
@@ -218,7 +216,6 @@ export const initializeServices = async ({
|
||||
);
|
||||
|
||||
const superSimpleQueueHelper = new SuperSimpleQueueHelper({
|
||||
db,
|
||||
logger,
|
||||
networkService,
|
||||
statusService,
|
||||
@@ -226,6 +223,7 @@ export const initializeServices = async ({
|
||||
checkService,
|
||||
buffer: bufferService,
|
||||
incidentService,
|
||||
maintenanceWindowsRepository,
|
||||
});
|
||||
|
||||
const superSimpleQueue = await SuperSimpleQueue.create({
|
||||
@@ -259,9 +257,9 @@ export const initializeServices = async ({
|
||||
errorService,
|
||||
});
|
||||
const maintenanceWindowService = new MaintenanceWindowService({
|
||||
db,
|
||||
errorService,
|
||||
monitorsRepository,
|
||||
maintenanceWindowsRepository,
|
||||
});
|
||||
const monitorService = new MonitorService({
|
||||
jobQueue: superSimpleQueue,
|
||||
@@ -307,6 +305,7 @@ export const initializeServices = async ({
|
||||
notificationsRepository,
|
||||
incidentsRepository,
|
||||
teamsRepository,
|
||||
maintenanceWindowsRepository,
|
||||
};
|
||||
|
||||
return services;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import {
|
||||
createMaintenanceWindowBodyValidation,
|
||||
editMaintenanceWindowByIdParamValidation,
|
||||
@@ -9,6 +8,7 @@ import {
|
||||
getMaintenanceWindowsByTeamIdQueryValidation,
|
||||
deleteMaintenanceWindowByIdParamValidation,
|
||||
} from "@/validation/joi.js";
|
||||
import { requireTeamId } from "@/controllers/controllerUtils.js";
|
||||
|
||||
const SERVICE_NAME = "maintenanceWindowController";
|
||||
|
||||
@@ -26,11 +26,7 @@ class MaintenanceWindowController {
|
||||
createMaintenanceWindows = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
await createMaintenanceWindowBodyValidation.validateAsync(req.body);
|
||||
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
await this.maintenanceWindowService.createMaintenanceWindow({ teamId, body: req.body });
|
||||
|
||||
@@ -46,10 +42,7 @@ class MaintenanceWindowController {
|
||||
try {
|
||||
await getMaintenanceWindowByIdParamValidation.validateAsync(req.params);
|
||||
|
||||
const teamId = req.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: req.params.id, teamId });
|
||||
|
||||
@@ -67,11 +60,7 @@ class MaintenanceWindowController {
|
||||
try {
|
||||
await getMaintenanceWindowsByTeamIdQueryValidation.validateAsync(req.query);
|
||||
|
||||
const teamId = req?.user?.teamId;
|
||||
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({ teamId, query: req.query });
|
||||
|
||||
@@ -89,10 +78,7 @@ class MaintenanceWindowController {
|
||||
try {
|
||||
await getMaintenanceWindowsByMonitorIdParamValidation.validateAsync(req.params);
|
||||
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({ monitorId: req.params.monitorId, teamId });
|
||||
|
||||
@@ -109,10 +95,7 @@ class MaintenanceWindowController {
|
||||
try {
|
||||
await deleteMaintenanceWindowByIdParamValidation.validateAsync(req.params);
|
||||
|
||||
const teamId = req?.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
await this.maintenanceWindowService.deleteMaintenanceWindow({ id: req.params.id, teamId });
|
||||
|
||||
@@ -130,10 +113,7 @@ class MaintenanceWindowController {
|
||||
await editMaintenanceWindowByIdParamValidation.validateAsync(req.params);
|
||||
await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body);
|
||||
|
||||
const teamId = req.user?.teamId;
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({ id: req.params.id, body: req.body, teamId });
|
||||
return res.status(200).json({
|
||||
|
||||
@@ -4,11 +4,9 @@ import { runMigrations } from "./migration/index.js";
|
||||
class MongoDB {
|
||||
static SERVICE_NAME = "MongoDB";
|
||||
|
||||
constructor({ logger, envSettings, statusPageModule, maintenanceWindowModule, notificationModule, incidentModule }) {
|
||||
constructor({ logger, envSettings, statusPageModule, incidentModule }) {
|
||||
this.logger = logger;
|
||||
this.envSettings = envSettings;
|
||||
this.maintenanceWindowModule = maintenanceWindowModule;
|
||||
this.notificationModule = notificationModule;
|
||||
this.statusPageModule = statusPageModule;
|
||||
this.incidentModule = incidentModule;
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import mongoose from "mongoose";
|
||||
/**
|
||||
* MaintenanceWindow Schema
|
||||
* @module MaintenanceWindow
|
||||
* @typedef {Object} MaintenanceWindow
|
||||
* @property {mongoose.Schema.Types.ObjectId} monitorId - The ID of the monitor. This is a reference to the Monitor model and is immutable.
|
||||
* @property {Boolean} active - Indicates whether the maintenance window is active.
|
||||
* @property {Number} repeat - Indicates how often this maintenance window should repeat.
|
||||
* @property {Date} start - The start date and time of the maintenance window.
|
||||
* @property {Date} end - The end date and time of the maintenance window.
|
||||
* @property {Date} expiry - The expiry date and time of the maintenance window. This is used for MongoDB's TTL index to automatically delete the document at this time. This field is set to the same value as `end` when `oneTime` is `true`.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* let maintenanceWindow = new MaintenanceWindow({
|
||||
* monitorId: monitorId,
|
||||
* active: active,
|
||||
* repeat: repeat,
|
||||
* start: start,
|
||||
* end: end,
|
||||
* });
|
||||
*
|
||||
* if (repeat === 0) {
|
||||
* maintenanceWindow.expiry = end;
|
||||
* }
|
||||
*
|
||||
*/
|
||||
|
||||
const MaintenanceWindow = mongoose.Schema(
|
||||
{
|
||||
monitorId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Monitor",
|
||||
immutable: true,
|
||||
},
|
||||
teamId: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "Team",
|
||||
immutable: true,
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
},
|
||||
repeat: {
|
||||
type: Number,
|
||||
},
|
||||
start: {
|
||||
type: Date,
|
||||
},
|
||||
end: {
|
||||
type: Date,
|
||||
},
|
||||
expiry: {
|
||||
type: Date,
|
||||
index: { expires: "0s" },
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
export default mongoose.model("MaintenanceWindow", MaintenanceWindow);
|
||||
@@ -0,0 +1,60 @@
|
||||
import { Schema, model, type Types } from "mongoose";
|
||||
import type { MaintenanceWindow } from "@/types/maintenanceWindow.js";
|
||||
|
||||
type MaintenanceWindowDocumentBase = Omit<MaintenanceWindow, "id" | "monitorId" | "teamId" | "start" | "end" | "createdAt" | "updatedAt"> & {
|
||||
monitorId: Types.ObjectId;
|
||||
teamId: Types.ObjectId;
|
||||
start: Date;
|
||||
end: Date;
|
||||
expiry?: Date;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
|
||||
interface MaintenanceWindowDocument extends MaintenanceWindowDocumentBase {
|
||||
_id: Types.ObjectId;
|
||||
}
|
||||
|
||||
const MaintenanceWindowSchema = new Schema<MaintenanceWindowDocument>(
|
||||
{
|
||||
monitorId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Monitor",
|
||||
immutable: true,
|
||||
},
|
||||
teamId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: "Team",
|
||||
immutable: true,
|
||||
},
|
||||
active: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
},
|
||||
repeat: {
|
||||
type: Number,
|
||||
},
|
||||
start: {
|
||||
type: Date,
|
||||
},
|
||||
end: {
|
||||
type: Date,
|
||||
},
|
||||
expiry: {
|
||||
type: Date,
|
||||
index: { expires: "0s" },
|
||||
},
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
}
|
||||
);
|
||||
|
||||
const MaintenanceWindowModel = model<MaintenanceWindowDocument>("MaintenanceWindow", MaintenanceWindowSchema);
|
||||
|
||||
export type { MaintenanceWindowDocument };
|
||||
export { MaintenanceWindowModel };
|
||||
export default MaintenanceWindowModel;
|
||||
@@ -30,3 +30,6 @@ export { default as IncidentModel } from "@/db/models/Incident.js";
|
||||
|
||||
export * from "@/db/models/Team.js";
|
||||
export { default as TeamModel } from "@/db/models/Team.js";
|
||||
|
||||
export * from "@/db/models/MaintenanceWindow.js";
|
||||
export { default as MaintenanceWindowModel } from "@/db/models/MaintenanceWindow.js";
|
||||
@@ -1,106 +0,0 @@
|
||||
const SERVICE_NAME = "maintenanceWindowModule";
|
||||
|
||||
class MaintenanceWindowModule {
|
||||
constructor({ MaintenanceWindow }) {
|
||||
this.MaintenanceWindow = MaintenanceWindow;
|
||||
}
|
||||
createMaintenanceWindow = async (maintenanceWindowData) => {
|
||||
try {
|
||||
const maintenanceWindow = new this.MaintenanceWindow({
|
||||
...maintenanceWindowData,
|
||||
});
|
||||
|
||||
// If the maintenance window is a one time window, set the expiry to the end date
|
||||
if (maintenanceWindowData.oneTime) {
|
||||
maintenanceWindow.expiry = maintenanceWindowData.end;
|
||||
}
|
||||
const result = await maintenanceWindow.save();
|
||||
return result;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "createMaintenanceWindow";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
getMaintenanceWindowById = async ({ id, teamId }) => {
|
||||
try {
|
||||
const maintenanceWindow = await this.MaintenanceWindow.findOne({
|
||||
_id: id,
|
||||
teamId: teamId,
|
||||
});
|
||||
return maintenanceWindow;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getMaintenanceWindowById";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
getMaintenanceWindowsByTeamId = async (teamId, query) => {
|
||||
try {
|
||||
let { active, page, rowsPerPage, field, order } = query || {};
|
||||
const maintenanceQuery = { teamId };
|
||||
|
||||
if (active !== undefined) maintenanceQuery.active = active;
|
||||
|
||||
const maintenanceWindowCount = await this.MaintenanceWindow.countDocuments(maintenanceQuery);
|
||||
|
||||
// Pagination
|
||||
let skip = 0;
|
||||
if (page && rowsPerPage) {
|
||||
skip = page * rowsPerPage;
|
||||
}
|
||||
|
||||
// Sorting
|
||||
let sort = {};
|
||||
if (field !== undefined && order !== undefined) {
|
||||
sort[field] = order === "asc" ? 1 : -1;
|
||||
}
|
||||
|
||||
const maintenanceWindows = await this.MaintenanceWindow.find(maintenanceQuery).skip(skip).limit(rowsPerPage).sort(sort);
|
||||
|
||||
return { maintenanceWindows, maintenanceWindowCount };
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getMaintenanceWindowByUserId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
getMaintenanceWindowsByMonitorId = async ({ monitorId, teamId }) => {
|
||||
try {
|
||||
const maintenanceWindows = await this.MaintenanceWindow.find({
|
||||
monitorId: monitorId,
|
||||
teamId: teamId,
|
||||
});
|
||||
return maintenanceWindows;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getMaintenanceWindowsByMonitorId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
deleteMaintenanceWindowById = async ({ id, teamId }) => {
|
||||
try {
|
||||
const maintenanceWindow = await this.MaintenanceWindow.findOneAndDelete({ _id: id, teamId });
|
||||
return maintenanceWindow;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "deleteMaintenanceWindowById";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
editMaintenanceWindowById = async ({ id, body }) => {
|
||||
try {
|
||||
const editedMaintenanceWindow = await this.MaintenanceWindow.findByIdAndUpdate(id, body, { new: true });
|
||||
return editedMaintenanceWindow;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "editMaintenanceWindowById";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MaintenanceWindowModule;
|
||||
@@ -1,98 +0,0 @@
|
||||
const SERVICE_NAME = "notificationModule";
|
||||
|
||||
class NotificationModule {
|
||||
constructor({ Notification, Monitor }) {
|
||||
this.Notification = Notification;
|
||||
this.Monitor = Monitor;
|
||||
}
|
||||
createNotification = async (notificationData) => {
|
||||
try {
|
||||
const notification = await new this.Notification({ ...notificationData }).save();
|
||||
return notification;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "createNotification";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
getNotificationsByTeamId = async (teamId) => {
|
||||
try {
|
||||
const notifications = await this.Notification.find({ teamId });
|
||||
return notifications;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getNotificationsByTeamId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
getNotificationsByIds = async (notificationIds) => {
|
||||
try {
|
||||
const notifications = await this.Notification.find({ _id: { $in: notificationIds } });
|
||||
return notifications;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getNotificationsByIds";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
getNotificationsByMonitorId = async (monitorId) => {
|
||||
try {
|
||||
const notifications = await this.Notification.find({ monitorId });
|
||||
return notifications;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getNotificationsByMonitorId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
deleteNotificationsByMonitorId = async (monitorId) => {
|
||||
try {
|
||||
const result = await this.Notification.deleteMany({ monitorId });
|
||||
return result.deletedCount;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "deleteNotificationsByMonitorId";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
deleteNotificationById = async (id) => {
|
||||
try {
|
||||
const notification = await this.Notification.findById(id);
|
||||
if (!notification) {
|
||||
throw new Error("Notification not found");
|
||||
}
|
||||
|
||||
const result = await this.Notification.findByIdAndDelete(id);
|
||||
await this.Monitor.updateMany({ notifications: id }, { $pull: { notifications: id } });
|
||||
return result;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "deleteNotificationById";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
getNotificationById = async (id) => {
|
||||
try {
|
||||
const notification = await this.Notification.findById(id);
|
||||
return notification;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "getNotificationById";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
editNotification = async (id, notificationData) => {
|
||||
try {
|
||||
const notification = await this.Notification.findByIdAndUpdate(id, notificationData, {
|
||||
new: true,
|
||||
});
|
||||
return notification;
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
error.method = "editNotification";
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default NotificationModule;
|
||||
@@ -30,3 +30,6 @@ export { default as MongoIncidentRepository } from "@/repositories/incidents/Mon
|
||||
|
||||
export * from "@/repositories/teams/ITeamsRepository.js";
|
||||
export { default as MongoTeamsRepository } from "@/repositories/teams/MongoTeamsRepository.js";
|
||||
|
||||
export * from "@/repositories/maintenance-windows/IMaintenanceWindowsRepository.js";
|
||||
export { default as MongoMaintenanceWindowsRepository } from "@/repositories/maintenance-windows/MongoMaintenanceWindowsRepository.js";
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { MaintenanceWindow } from "@/types/index.js";
|
||||
export interface IMaintenanceWindowsRepository {
|
||||
// create
|
||||
create(data: Partial<MaintenanceWindow>): Promise<MaintenanceWindow>;
|
||||
// fetch
|
||||
findById(id: string, teamId: string): Promise<MaintenanceWindow>;
|
||||
findByMonitorId(monitorId: string, teamId: string): Promise<MaintenanceWindow[]>;
|
||||
findByTeamId(teamId: string, active: boolean, page: number, rowsPerPage: number, field: string, order: string): Promise<MaintenanceWindow[]>;
|
||||
|
||||
// update
|
||||
updateById(id: string, teamId: string, data: Partial<MaintenanceWindow>): Promise<MaintenanceWindow>;
|
||||
// delete
|
||||
deleteById(id: string, teamId: string): Promise<MaintenanceWindow>;
|
||||
// other
|
||||
countByTeamId(teamId: string, active: boolean): Promise<number>;
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
import type { MaintenanceWindow } from "@/types/index.js";
|
||||
import { type MaintenanceWindowDocument, MaintenanceWindowModel } from "@/db/models/index.js";
|
||||
import { IMaintenanceWindowsRepository } from "./IMaintenanceWindowsRepository.js";
|
||||
import mongoose from "mongoose";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
|
||||
class MongoMaintenanceWindowsRepository implements IMaintenanceWindowsRepository {
|
||||
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 mapDocuments = (documents: MaintenanceWindowDocument[]): MaintenanceWindow[] => {
|
||||
if (!documents?.length) {
|
||||
return [];
|
||||
}
|
||||
return documents.map((doc) => this.toEntity(doc));
|
||||
};
|
||||
|
||||
private toEntity = (doc: MaintenanceWindowDocument): MaintenanceWindow => {
|
||||
return {
|
||||
id: this.toStringId(doc._id),
|
||||
monitorId: this.toStringId(doc.monitorId),
|
||||
teamId: this.toStringId(doc.teamId),
|
||||
active: doc.active,
|
||||
name: doc.name,
|
||||
repeat: doc.repeat,
|
||||
start: this.toDateString(doc.start),
|
||||
end: this.toDateString(doc.end),
|
||||
createdAt: this.toDateString(doc.createdAt),
|
||||
updatedAt: this.toDateString(doc.updatedAt),
|
||||
};
|
||||
};
|
||||
|
||||
create = async (data: Partial<MaintenanceWindow>): Promise<MaintenanceWindow> => {
|
||||
const maintenanceWindow = new MaintenanceWindowModel(data);
|
||||
|
||||
// If the maintenance window is a one time window, set the expiry to the end date
|
||||
if (maintenanceWindow.repeat === 0) {
|
||||
maintenanceWindow.expiry = maintenanceWindow.end;
|
||||
}
|
||||
const result = await maintenanceWindow.save();
|
||||
return this.toEntity(result);
|
||||
};
|
||||
|
||||
findById = async (id: string, teamId: string): Promise<MaintenanceWindow> => {
|
||||
const maintenanceWindow = await MaintenanceWindowModel.findOne({
|
||||
_id: id,
|
||||
teamId: teamId,
|
||||
});
|
||||
if (!maintenanceWindow) {
|
||||
throw new AppError({ message: "Maintenance Window not found", status: 404 });
|
||||
}
|
||||
return this.toEntity(maintenanceWindow);
|
||||
};
|
||||
|
||||
findByMonitorId = async (monitorId: string, teamId: string): Promise<MaintenanceWindow[]> => {
|
||||
const maintenanceWindows = await MaintenanceWindowModel.find({
|
||||
monitorId: monitorId,
|
||||
teamId: teamId,
|
||||
});
|
||||
return this.mapDocuments(maintenanceWindows);
|
||||
};
|
||||
|
||||
findByTeamId = async (
|
||||
teamId: string,
|
||||
active: boolean,
|
||||
page: number,
|
||||
rowsPerPage: number,
|
||||
field: string,
|
||||
order: string
|
||||
): Promise<MaintenanceWindow[]> => {
|
||||
const maintenanceQuery: Record<string, any> = { teamId };
|
||||
|
||||
if (active !== undefined) maintenanceQuery.active = active;
|
||||
|
||||
// Pagination
|
||||
let skip = 0;
|
||||
if (page && rowsPerPage) {
|
||||
skip = page * rowsPerPage;
|
||||
}
|
||||
|
||||
// Sorting
|
||||
let sort: Record<string, any> = {};
|
||||
if (field !== undefined && order !== undefined) {
|
||||
sort[field] = order === "asc" ? 1 : -1;
|
||||
}
|
||||
|
||||
const maintenanceWindows = await MaintenanceWindowModel.find(maintenanceQuery).skip(skip).limit(rowsPerPage).sort(sort);
|
||||
return this.mapDocuments(maintenanceWindows);
|
||||
};
|
||||
|
||||
updateById = async (id: string, teamId: string, data: Partial<MaintenanceWindow>): Promise<MaintenanceWindow> => {
|
||||
const updated = await MaintenanceWindowModel.findOneAndUpdate(
|
||||
{
|
||||
_id: new mongoose.Types.ObjectId(id),
|
||||
teamId: new mongoose.Types.ObjectId(teamId),
|
||||
},
|
||||
{ $set: data },
|
||||
{ new: true, runValidators: true }
|
||||
);
|
||||
if (!updated) {
|
||||
throw new AppError({ message: "Maintenance window not found or could not be updated", status: 404 });
|
||||
}
|
||||
return this.toEntity(updated);
|
||||
};
|
||||
|
||||
deleteById = async (id: string, teamId: string): Promise<MaintenanceWindow> => {
|
||||
const deleted = await MaintenanceWindowModel.findOneAndDelete({
|
||||
_id: new mongoose.Types.ObjectId(id),
|
||||
teamId: new mongoose.Types.ObjectId(teamId),
|
||||
});
|
||||
if (!deleted) {
|
||||
throw new AppError({ message: "Maintenance window not found or could not be deleted", status: 404 });
|
||||
}
|
||||
return this.toEntity(deleted);
|
||||
};
|
||||
countByTeamId = async (teamId: string, active: boolean) => {
|
||||
const maintenanceQuery: Record<string, any> = { teamId };
|
||||
|
||||
if (active !== undefined) maintenanceQuery.active = active;
|
||||
|
||||
return await MaintenanceWindowModel.countDocuments(maintenanceQuery);
|
||||
};
|
||||
}
|
||||
|
||||
export default MongoMaintenanceWindowsRepository;
|
||||
@@ -1,17 +1,26 @@
|
||||
import { IMonitorsRepository } from "@/repositories/index.js";
|
||||
import { IMaintenanceWindowsRepository, IMonitorsRepository } from "@/repositories/index.js";
|
||||
import { ParseBoolean } from "@/utils/utils.js";
|
||||
|
||||
const SERVICE_NAME = "maintenanceWindowService";
|
||||
|
||||
class MaintenanceWindowService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private db: any;
|
||||
private errorService: any;
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
private maintenanceWindowsRepository: IMaintenanceWindowsRepository;
|
||||
|
||||
constructor({ db, errorService, monitorsRepository }: { db: any; errorService: any; monitorsRepository: IMonitorsRepository }) {
|
||||
this.db = db;
|
||||
constructor({
|
||||
errorService,
|
||||
monitorsRepository,
|
||||
maintenanceWindowsRepository,
|
||||
}: {
|
||||
errorService: any;
|
||||
monitorsRepository: IMonitorsRepository;
|
||||
maintenanceWindowsRepository: IMaintenanceWindowsRepository;
|
||||
}) {
|
||||
this.errorService = errorService;
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
this.maintenanceWindowsRepository = maintenanceWindowsRepository;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
@@ -29,7 +38,7 @@ class MaintenanceWindowService {
|
||||
}
|
||||
|
||||
const dbTransactions = monitorIds.map((monitorId: string) => {
|
||||
return this.db.maintenanceWindowModule.createMaintenanceWindow({
|
||||
return this.maintenanceWindowsRepository.create({
|
||||
teamId,
|
||||
monitorId,
|
||||
name: body.name,
|
||||
@@ -43,27 +52,31 @@ class MaintenanceWindowService {
|
||||
};
|
||||
|
||||
getMaintenanceWindowById = async ({ id, teamId }: { id: string; teamId: string }) => {
|
||||
const maintenanceWindow = await this.db.maintenanceWindowModule.getMaintenanceWindowById({ id, teamId });
|
||||
return maintenanceWindow;
|
||||
return await this.maintenanceWindowsRepository.findById(id, teamId);
|
||||
};
|
||||
|
||||
getMaintenanceWindowsByTeamId = async ({ teamId, query }: { teamId: string; query: any }) => {
|
||||
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByTeamId(teamId, query);
|
||||
return maintenanceWindows;
|
||||
let { active, page, rowsPerPage, field, order } = query || {};
|
||||
|
||||
active = typeof active === "undefined" ? undefined : ParseBoolean(active);
|
||||
page = parseInt(page) || 0;
|
||||
rowsPerPage = parseInt(rowsPerPage) || 10;
|
||||
|
||||
const maintenanceWindows = await this.maintenanceWindowsRepository.findByTeamId(teamId, active, page, rowsPerPage, field, order);
|
||||
const maintenanceWindowCount = await this.maintenanceWindowsRepository.countByTeamId(teamId, active);
|
||||
return { maintenanceWindows, maintenanceWindowCount };
|
||||
};
|
||||
|
||||
getMaintenanceWindowsByMonitorId = async ({ monitorId, teamId }: { monitorId: string; teamId: string }) => {
|
||||
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId({ monitorId, teamId });
|
||||
return maintenanceWindows;
|
||||
return await this.maintenanceWindowsRepository.findByMonitorId(monitorId, teamId);
|
||||
};
|
||||
|
||||
deleteMaintenanceWindow = async ({ id, teamId }: { id: string; teamId: string }) => {
|
||||
await this.db.maintenanceWindowModule.deleteMaintenanceWindowById({ id, teamId });
|
||||
return await this.maintenanceWindowsRepository.deleteById(id, teamId);
|
||||
};
|
||||
|
||||
editMaintenanceWindow = async ({ id, teamId, body }: { id: string; teamId: string; body: any }) => {
|
||||
const editedMaintenanceWindow = await this.db.maintenanceWindowModule.editMaintenanceWindowById({ id, body, teamId });
|
||||
return editedMaintenanceWindow;
|
||||
return await this.maintenanceWindowsRepository.updateById(id, teamId, body);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@ import type { Monitor } from "@/types/monitor.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { INetworkService, INotificationsService, IStatusService } from "@/service/index.js";
|
||||
import IncidentService from "@/service/business/incidentService.js";
|
||||
import { IMaintenanceWindowsRepository } from "@/repositories/index.js";
|
||||
|
||||
class SuperSimpleQueueHelper {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private db: any;
|
||||
private logger: any;
|
||||
private networkService: INetworkService;
|
||||
private statusService: IStatusService;
|
||||
@@ -15,9 +15,9 @@ class SuperSimpleQueueHelper {
|
||||
private checkService: any;
|
||||
private buffer: any;
|
||||
private incidentService: IncidentService;
|
||||
private maintenanceWindowsRepository: IMaintenanceWindowsRepository;
|
||||
|
||||
constructor({
|
||||
db,
|
||||
logger,
|
||||
networkService,
|
||||
statusService,
|
||||
@@ -25,8 +25,8 @@ class SuperSimpleQueueHelper {
|
||||
checkService,
|
||||
buffer,
|
||||
incidentService,
|
||||
maintenanceWindowsRepository,
|
||||
}: {
|
||||
db: any;
|
||||
logger: any;
|
||||
networkService: INetworkService;
|
||||
statusService: IStatusService;
|
||||
@@ -34,8 +34,8 @@ class SuperSimpleQueueHelper {
|
||||
checkService: any;
|
||||
buffer: any;
|
||||
incidentService: IncidentService;
|
||||
maintenanceWindowsRepository: IMaintenanceWindowsRepository;
|
||||
}) {
|
||||
this.db = db;
|
||||
this.logger = logger;
|
||||
this.networkService = networkService;
|
||||
this.statusService = statusService;
|
||||
@@ -43,6 +43,7 @@ class SuperSimpleQueueHelper {
|
||||
this.buffer = buffer;
|
||||
this.notificationsService = notificationsService;
|
||||
this.incidentService = incidentService;
|
||||
this.maintenanceWindowsRepository = maintenanceWindowsRepository;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
@@ -122,10 +123,7 @@ class SuperSimpleQueueHelper {
|
||||
};
|
||||
|
||||
async isInMaintenanceWindow(monitorId: string, teamId: string) {
|
||||
const maintenanceWindows = await this.db.maintenanceWindowModule.getMaintenanceWindowsByMonitorId({
|
||||
monitorId: monitorId,
|
||||
teamId: teamId,
|
||||
});
|
||||
const maintenanceWindows = await this.maintenanceWindowsRepository.findByMonitorId(monitorId, teamId);
|
||||
// Check for active maintenance window:
|
||||
const maintenanceWindowIsActive = maintenanceWindows.reduce((acc: any, window: any) => {
|
||||
if (window.active) {
|
||||
|
||||
@@ -12,3 +12,4 @@ export * from "@/types/alert.js";
|
||||
export * from "@/types/incident.js";
|
||||
export * from "@/types/email.js";
|
||||
export * from "@/types/team.js";
|
||||
export * from "@/types/maintenanceWindow.js";
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface MaintenanceWindow {
|
||||
id: string;
|
||||
monitorId: String;
|
||||
teamId: string;
|
||||
active: boolean;
|
||||
name: string;
|
||||
repeat: number;
|
||||
start: string;
|
||||
end: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
@@ -5,21 +5,24 @@ import type { Monitor } from "../src/types/monitor.ts";
|
||||
const createLogger = () => ({ info: jest.fn(), error: jest.fn(), warn: jest.fn(), debug: jest.fn() });
|
||||
|
||||
const createHelper = (overrides?: Partial<ConstructorParameters<typeof SuperSimpleQueueHelper>[0]>) => {
|
||||
const maintenanceWindowModule = {
|
||||
getMaintenanceWindowsByMonitorId: jest.fn().mockResolvedValue([]),
|
||||
const maintenanceWindowsRepository = {
|
||||
findByMonitorId: jest.fn().mockResolvedValue([]),
|
||||
};
|
||||
const statusServiceMock = {
|
||||
updateMonitorStatus: jest.fn().mockResolvedValue({ monitor: { id: "m1" }, statusChanged: true, prevStatus: false }),
|
||||
};
|
||||
const helper = new SuperSimpleQueueHelper({
|
||||
db: { maintenanceWindowModule },
|
||||
logger: createLogger(),
|
||||
networkService: { requestStatus: jest.fn() },
|
||||
statusService: statusServiceMock,
|
||||
notificationService: { handleNotifications: jest.fn().mockResolvedValue(undefined) },
|
||||
notificationsService: { handleNotifications: jest.fn().mockResolvedValue(undefined) },
|
||||
checkService: { buildCheck: jest.fn().mockResolvedValue({}) },
|
||||
buffer: { addToBuffer: jest.fn() },
|
||||
incidentService: { handleIncident: jest.fn().mockResolvedValue(undefined) },
|
||||
maintenanceWindowsRepository,
|
||||
...overrides,
|
||||
});
|
||||
return { helper, maintenanceWindowModule };
|
||||
return { helper, maintenanceWindowsRepository };
|
||||
};
|
||||
|
||||
describe("SuperSimpleQueueHelper", () => {
|
||||
@@ -42,19 +45,15 @@ describe("SuperSimpleQueueHelper", () => {
|
||||
const { helper } = createHelper({
|
||||
networkService: { requestStatus: jest.fn().mockResolvedValue(networkResponse) },
|
||||
statusService: {
|
||||
updateMonitorStatus: jest.fn().mockResolvedValue({ monitor: updatedMonitor, statusChanged: true, prevStatus: false }),
|
||||
updateMonitorStatus: jest.fn().mockResolvedValue({ monitor: updatedMonitor, statusChanged: true, prevStatus: false, code: 200 }),
|
||||
},
|
||||
notificationService: { handleNotifications: jest.fn().mockResolvedValue(undefined) },
|
||||
notificationsService: { handleNotifications: jest.fn().mockResolvedValue(undefined) },
|
||||
});
|
||||
jest.spyOn(helper, "isInMaintenanceWindow").mockResolvedValue(false);
|
||||
const job = helper.getMonitorJob();
|
||||
const monitor = { id: "m1", teamId: "team" } as Monitor;
|
||||
await job(monitor);
|
||||
expect(helper["networkService"].requestStatus).toHaveBeenCalledWith(monitor);
|
||||
expect(helper["statusService"].updateMonitorStatus).toHaveBeenCalledWith(networkResponse);
|
||||
expect(helper["notificationService"].handleNotifications).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ monitor: updatedMonitor, statusChanged: true, prevStatus: false })
|
||||
);
|
||||
});
|
||||
|
||||
it("throws when monitor id is missing", async () => {
|
||||
@@ -68,8 +67,8 @@ describe("SuperSimpleQueueHelper", () => {
|
||||
describe("isInMaintenanceWindow", () => {
|
||||
it("returns true when an active window spans now", async () => {
|
||||
const now = new Date();
|
||||
const { helper, maintenanceWindowModule } = createHelper();
|
||||
maintenanceWindowModule.getMaintenanceWindowsByMonitorId.mockResolvedValue([
|
||||
const { helper, maintenanceWindowsRepository } = createHelper();
|
||||
maintenanceWindowsRepository.findByMonitorId.mockResolvedValue([
|
||||
{
|
||||
active: true,
|
||||
start: new Date(now.getTime() - 1000).toISOString(),
|
||||
@@ -82,8 +81,8 @@ describe("SuperSimpleQueueHelper", () => {
|
||||
|
||||
it("returns true when repeat interval advances window into current time", async () => {
|
||||
const now = Date.now();
|
||||
const { helper, maintenanceWindowModule } = createHelper();
|
||||
maintenanceWindowModule.getMaintenanceWindowsByMonitorId.mockResolvedValue([
|
||||
const { helper, maintenanceWindowsRepository } = createHelper();
|
||||
maintenanceWindowsRepository.findByMonitorId.mockResolvedValue([
|
||||
{
|
||||
active: true,
|
||||
start: new Date(now - 7200000).toISOString(),
|
||||
|
||||
Reference in new issue
Block a user