mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-25 19:29:39 -06:00
Merge pull request #2978 from bluewave-labs/feat/v2/routes
add v2 routes, controllers
This commit is contained in:
@@ -2,7 +2,7 @@ import { createCommonDependencies } from "../controllers/v1/baseController.js";
|
||||
|
||||
// Services
|
||||
|
||||
// Controllers
|
||||
// V1 Controllers
|
||||
import MonitorController from "../controllers/v1/monitorController.js";
|
||||
import AuthController from "../controllers/v1/authController.js";
|
||||
import SettingsController from "../controllers/v1/settingsController.js";
|
||||
@@ -15,10 +15,18 @@ import StatusPageController from "../controllers/v1/statusPageController.js";
|
||||
import NotificationController from "../controllers/v1/notificationController.js";
|
||||
import DiagnosticController from "../controllers/v1/diagnosticController.js";
|
||||
|
||||
// V2 Controllers
|
||||
import AuthControllerV2 from "../controllers/v2/AuthController.js";
|
||||
import InviteControllerV2 from "../controllers/v2/InviteController.js";
|
||||
import MaintenanceControllerV2 from "../controllers/v2/MaintenanceController.js";
|
||||
import MonitorControllerV2 from "../controllers/v2/MonitorController.js";
|
||||
import NotificationChannelControllerV2 from "../controllers/v2/NotificationChannelController.js";
|
||||
import QueueControllerV2 from "../controllers/v2/QueueController.js";
|
||||
export const initializeControllers = (services) => {
|
||||
const controllers = {};
|
||||
const commonDependencies = createCommonDependencies(services.db, services.errorService, services.logger, services.stringService);
|
||||
|
||||
// V1
|
||||
controllers.authController = new AuthController(commonDependencies, {
|
||||
settingsService: services.settingsService,
|
||||
emailService: services.emailService,
|
||||
@@ -62,5 +70,13 @@ export const initializeControllers = (services) => {
|
||||
diagnosticService: services.diagnosticService,
|
||||
});
|
||||
|
||||
//V2
|
||||
controllers.authControllerV2 = new AuthControllerV2(services.authServiceV2, services.inviteServiceV2);
|
||||
controllers.inviteControllerV2 = new InviteControllerV2(services.inviteServiceV2);
|
||||
controllers.maintenanceControllerV2 = new MaintenanceControllerV2(services.maintenanceServiceV2);
|
||||
controllers.monitorControllerV2 = new MonitorControllerV2(services.monitorServiceV2);
|
||||
controllers.notificationChannelControllerV2 = new NotificationChannelControllerV2(services.notificationChannelServiceV2);
|
||||
controllers.queueControllerV2 = new QueueControllerV2(services.jobQueueV2);
|
||||
|
||||
return controllers;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,16 @@ import LogRoutes from "../routes/v1/logRoutes.js";
|
||||
import DiagnosticRoutes from "../routes/v1//diagnosticRoute.js";
|
||||
import NotificationRoutes from "../routes/v1/notificationRoute.js";
|
||||
|
||||
//V2
|
||||
import AuthRoutesV2 from "../routes/v2/auth.js";
|
||||
import InviteRoutesV2 from "../routes/v2/invite.js";
|
||||
import MaintenanceRoutesV2 from "../routes/v2/maintenance.js";
|
||||
import MonitorRoutesV2 from "../routes/v2/monitors.js";
|
||||
import NotificationChannelRoutesV2 from "../routes/v2/notificationChannels.js";
|
||||
import QueueRoutesV2 from "../routes/v2/queue.js";
|
||||
|
||||
export const setupRoutes = (app, controllers) => {
|
||||
// V1
|
||||
const authRoutes = new AuthRoutes(controllers.authController);
|
||||
const monitorRoutes = new MonitorRoutes(controllers.monitorController);
|
||||
const settingsRoutes = new SettingsRoutes(controllers.settingsController);
|
||||
@@ -37,4 +46,19 @@ export const setupRoutes = (app, controllers) => {
|
||||
app.use("/api/v1/status-page", statusPageRoutes.getRouter());
|
||||
app.use("/api/v1/notifications", verifyJWT, notificationRoutes.getRouter());
|
||||
app.use("/api/v1/diagnostic", verifyJWT, diagnosticRoutes.getRouter());
|
||||
|
||||
// V2
|
||||
const authRoutesV2 = new AuthRoutesV2(controllers.authControllerV2);
|
||||
const inviteRoutesV2 = new InviteRoutesV2(controllers.inviteControllerV2);
|
||||
const maintenanceRoutesV2 = new MaintenanceRoutesV2(controllers.maintenanceControllerV2);
|
||||
const monitorRoutesV2 = new MonitorRoutesV2(controllers.monitorControllerV2);
|
||||
const notificationChannelRoutesV2 = new NotificationChannelRoutesV2(controllers.notificationChannelControllerV2);
|
||||
const queueRoutesV2 = new QueueRoutesV2(controllers.queueControllerV2);
|
||||
|
||||
app.use("/api/v2/auth", authApiLimiter, authRoutesV2.getRouter());
|
||||
app.use("/api/v2/invite", inviteRoutesV2.getRouter());
|
||||
app.use("/api/v2/maintenance", maintenanceRoutesV2.getRouter());
|
||||
app.use("/api/v2/monitors", monitorRoutesV2.getRouter());
|
||||
app.use("/api/v2/notification-channels", notificationChannelRoutesV2.getRouter());
|
||||
app.use("/api/v2/queue", queueRoutesV2.getRouter());
|
||||
};
|
||||
|
||||
@@ -61,13 +61,35 @@ import AppSettings from "../db/v1/models/AppSettings.js";
|
||||
import InviteModule from "../db/v1/modules/inviteModule.js";
|
||||
import CheckModule from "../db/v1/modules/checkModule.js";
|
||||
import StatusPageModule from "../db/v1/modules/statusPageModule.js";
|
||||
import UserModule from "../db/v1/modules//userModule.js";
|
||||
import UserModule from "../db/v1/modules/userModule.js";
|
||||
import MaintenanceWindowModule from "../db/v1/modules/maintenanceWindowModule.js";
|
||||
import MonitorModule from "../db/v1/modules/monitorModule.js";
|
||||
import NotificationModule from "../db/v1/modules/notificationModule.js";
|
||||
import RecoveryModule from "../db/v1/modules/recoveryModule.js";
|
||||
import SettingsModule from "../db/v1/modules/settingsModule.js";
|
||||
|
||||
// V2 Business
|
||||
import AuthServiceV2 from "../service/v2/business/AuthService.js";
|
||||
import CheckServiceV2 from "../service/v2/business/CheckService.js";
|
||||
import InviteServiceV2 from "../service/v2/business/InviteService.js";
|
||||
import MaintenanceServiceV2 from "../service/v2/business/MaintenanceService.js";
|
||||
import MonitorServiceV2 from "../service/v2/business/MonitorService.js";
|
||||
import MonitorStatsServiceV2 from "../service/v2/business/MonitorStatsService.js";
|
||||
import NotificationChannelServiceV2 from "../service/v2/business/NotificationChannelService.js";
|
||||
import QueueServiceV2 from "../service/v2/business/QueueService.js";
|
||||
import UserServiceV2 from "../service/v2/business/UserService.js";
|
||||
|
||||
// V2 Infra
|
||||
import DiscordServiceV2 from "../service/v2/infrastructure/NotificationServices/Discord.js";
|
||||
import EmailServiceV2 from "../service/v2/infrastructure/NotificationServices/Email.js";
|
||||
import SlackServiceV2 from "../service/v2/infrastructure/NotificationServices/Slack.js";
|
||||
import WebhookServiceV2 from "../service/v2/infrastructure/NotificationServices/Webhook.js";
|
||||
import JobGeneratorV2 from "../service/v2/infrastructure/JobGenerator.js";
|
||||
import JobQueueV2 from "../service/v2/infrastructure/JobQueue.js";
|
||||
import NetworkServiceV2 from "../service/v2/infrastructure/NetworkService.js";
|
||||
import NotificationServiceV2 from "../service/v2/infrastructure/NotificationService.js";
|
||||
import StatusServiceV2 from "../service/v2/infrastructure/StatusService.js";
|
||||
|
||||
export const initializeServices = async ({ logger, envSettings, settingsService }) => {
|
||||
const serviceRegistry = new ServiceRegistry({ logger });
|
||||
ServiceRegistry.instance = serviceRegistry;
|
||||
@@ -208,7 +230,37 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
||||
games,
|
||||
});
|
||||
|
||||
// V2 Services
|
||||
const jobQueueV2 = new JobQueueV2();
|
||||
const authServiceV2 = new AuthServiceV2(jobQueueV2);
|
||||
const checkServiceV2 = new CheckServiceV2();
|
||||
const inviteServiceV2 = new InviteServiceV2();
|
||||
const maintenanceServiceV2 = new MaintenanceServiceV2();
|
||||
const monitorServiceV2 = new MonitorServiceV2(jobQueueV2);
|
||||
const monitorStatsServiceV2 = new MonitorStatsServiceV2();
|
||||
const notificationChannelServiceV2 = new NotificationChannelServiceV2();
|
||||
const queueServiceV2 = new QueueServiceV2(jobQueueV2);
|
||||
const userServiceV2 = new UserServiceV2();
|
||||
|
||||
// V2 Infra
|
||||
const discordServiceV2 = new DiscordServiceV2();
|
||||
const emailServiceV2 = new EmailServiceV2(userServiceV2);
|
||||
const slackServiceV2 = new SlackServiceV2();
|
||||
const webhookServiceV2 = new WebhookServiceV2();
|
||||
const networkServiceV2 = new NetworkServiceV2();
|
||||
const statusServiceV2 = new StatusServiceV2();
|
||||
const notificationServiceV2 = new NotificationServiceV2(userServiceV2);
|
||||
const jobGeneratorV2 = new JobGeneratorV2(
|
||||
networkServiceV2,
|
||||
checkServiceV2,
|
||||
monitorStatsServiceV2,
|
||||
statusServiceV2,
|
||||
notificationServiceV2,
|
||||
maintenanceServiceV2
|
||||
);
|
||||
|
||||
const services = {
|
||||
//v1
|
||||
settingsService,
|
||||
translationService,
|
||||
stringService,
|
||||
@@ -227,6 +279,25 @@ export const initializeServices = async ({ logger, envSettings, settingsService
|
||||
monitorService,
|
||||
errorService,
|
||||
logger,
|
||||
//v2
|
||||
jobQueueV2,
|
||||
authServiceV2,
|
||||
checkServiceV2,
|
||||
inviteServiceV2,
|
||||
maintenanceServiceV2,
|
||||
monitorServiceV2,
|
||||
monitorStatsServiceV2,
|
||||
notificationChannelServiceV2,
|
||||
queueServiceV2,
|
||||
userServiceV2,
|
||||
discordServiceV2,
|
||||
emailServiceV2,
|
||||
slackServiceV2,
|
||||
webhookServiceV2,
|
||||
networkServiceV2,
|
||||
statusServiceV2,
|
||||
notificationServiceV2,
|
||||
jobGeneratorV2,
|
||||
};
|
||||
|
||||
Object.values(services).forEach((service) => {
|
||||
|
||||
150
server/src/controllers/v2/AuthController.ts
Normal file
150
server/src/controllers/v2/AuthController.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { encode, decode } from "../../utils/JWTUtils.js";
|
||||
import AuthService from "../../service/v2/business/AuthService.js";
|
||||
import ApiError from "../../utils/ApiError.js";
|
||||
import InviteService from "../../service/v2/business/InviteService.js";
|
||||
import { IInvite } from "../../db/v2/models/index.js";
|
||||
|
||||
class AuthController {
|
||||
private authService: AuthService;
|
||||
private inviteService: InviteService;
|
||||
constructor(authService: AuthService, inviteService: InviteService) {
|
||||
this.authService = authService;
|
||||
this.inviteService = inviteService;
|
||||
}
|
||||
|
||||
register = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { email, firstName, lastName, password } = req.body;
|
||||
|
||||
if (!email || !firstName || !lastName || !password) {
|
||||
throw new Error("Email, firstName, lastName, and password are required");
|
||||
}
|
||||
|
||||
const result = await this.authService.register({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
});
|
||||
|
||||
const token = encode(result);
|
||||
|
||||
res.cookie("token", token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "strict",
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week
|
||||
});
|
||||
|
||||
res.status(201).json({
|
||||
message: "User created successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
registerWithInvite = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const token = req.params.token;
|
||||
if (!token) {
|
||||
throw new ApiError("Invite token is required", 400);
|
||||
}
|
||||
|
||||
const invite: IInvite = await this.inviteService.get(token);
|
||||
|
||||
const { firstName, lastName, password } = req.body;
|
||||
const email = invite?.email;
|
||||
const roles = invite?.roles;
|
||||
|
||||
if (!email || !firstName || !lastName || !password || !roles || roles.length === 0) {
|
||||
throw new Error("Email, firstName, lastName, password, and roles are required");
|
||||
}
|
||||
|
||||
const result = await this.authService.registerWithInvite({
|
||||
email,
|
||||
firstName,
|
||||
lastName,
|
||||
password,
|
||||
roles,
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
throw new Error("Registration failed");
|
||||
}
|
||||
|
||||
await this.inviteService.delete(invite._id.toString());
|
||||
|
||||
const jwt = encode(result);
|
||||
|
||||
res.cookie("token", jwt, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "strict",
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week
|
||||
});
|
||||
|
||||
res.status(201).json({ message: "User created successfully" });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
login = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
// Validation
|
||||
if (!email || !password) {
|
||||
return res.status(400).json({ message: "Email and password are required" });
|
||||
}
|
||||
const result = await this.authService.login({ email, password });
|
||||
|
||||
const token = encode(result);
|
||||
|
||||
res.cookie("token", token, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "strict",
|
||||
maxAge: 7 * 24 * 60 * 60 * 1000, // 1 week
|
||||
});
|
||||
|
||||
res.status(200).json({
|
||||
message: "Login successful",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
logout = (req: Request, res: Response) => {
|
||||
res.clearCookie("token", {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === "production",
|
||||
sameSite: "strict",
|
||||
});
|
||||
res.status(200).json({ message: "Logout successful" });
|
||||
};
|
||||
|
||||
me = (req: Request, res: Response, next: NextFunction) => {
|
||||
return res.status(200).json({ message: "OK" });
|
||||
};
|
||||
|
||||
cleanup = async (req: Request, res: Response) => {
|
||||
try {
|
||||
await this.authService.cleanup();
|
||||
res.status(200).json({ message: "Cleanup successful" });
|
||||
} catch (error) {}
|
||||
};
|
||||
|
||||
cleanMonitors = async (req: Request, res: Response) => {
|
||||
try {
|
||||
await this.authService.cleanMonitors();
|
||||
res.status(200).json({ message: "Monitors cleanup successful" });
|
||||
} catch (error) {
|
||||
res.status(500).json({ message: "Internal server error" });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default AuthController;
|
||||
62
server/src/controllers/v2/InviteController.ts
Normal file
62
server/src/controllers/v2/InviteController.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import InviteService from "../../service/v2/business/InviteService.js";
|
||||
|
||||
class InviteController {
|
||||
private inviteService: InviteService;
|
||||
constructor(inviteService: InviteService) {
|
||||
this.inviteService = inviteService;
|
||||
}
|
||||
|
||||
create = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
const invite = await this.inviteService.create(tokenizedUser, req.body);
|
||||
res.status(201).json({ message: "OK", data: invite });
|
||||
} catch (error: any) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getAll = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const invites = await this.inviteService.getAll();
|
||||
res.status(200).json({
|
||||
message: "OK",
|
||||
data: invites,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
get = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const token = req.params.token;
|
||||
if (!token) {
|
||||
return res.status(400).json({ message: "Token parameter is required" });
|
||||
}
|
||||
const invite = await this.inviteService.get(token);
|
||||
res.status(200).json({ message: "OK", data: invite });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
delete = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
return res.status(400).json({ message: "ID parameter is required" });
|
||||
}
|
||||
await this.inviteService.delete(id);
|
||||
res.status(204).json({ message: "OK" });
|
||||
} catch (error: any) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default InviteController;
|
||||
96
server/src/controllers/v2/MaintenanceController.ts
Normal file
96
server/src/controllers/v2/MaintenanceController.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import MaintenanceService from "../../service/v2/business/MaintenanceService.js";
|
||||
|
||||
class MaintenanceController {
|
||||
private maintenanceService: MaintenanceService;
|
||||
constructor(maintenanceService: MaintenanceService) {
|
||||
this.maintenanceService = maintenanceService;
|
||||
}
|
||||
|
||||
create = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
const maintenance = await this.maintenanceService.create(tokenizedUser, req.body);
|
||||
res.status(201).json({ message: "OK", data: maintenance });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getAll = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const maintenances = await this.maintenanceService.getAll();
|
||||
res.status(200).json({
|
||||
message: "OK",
|
||||
data: maintenances,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
toggleActive = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
return res.status(400).json({ message: "ID parameter is required" });
|
||||
}
|
||||
const maintenance = await this.maintenanceService.toggleActive(tokenizedUser, id);
|
||||
res.status(200).json({ message: "OK", data: maintenance });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
update = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
return res.status(400).json({ message: "ID parameter is required" });
|
||||
}
|
||||
const updatedMaintenance = await this.maintenanceService.update(tokenizedUser, id, req.body);
|
||||
res.status(200).json({ message: "OK", data: updatedMaintenance });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
get = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
return res.status(400).json({ message: "ID parameter is required" });
|
||||
}
|
||||
const maintenance = await this.maintenanceService.get(id);
|
||||
res.status(200).json({ message: "OK", data: maintenance });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
delete = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
return res.status(400).json({ message: "ID parameter is required" });
|
||||
}
|
||||
await this.maintenanceService.delete(id);
|
||||
res.status(204).json({ message: "OK" });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MaintenanceController;
|
||||
157
server/src/controllers/v2/MonitorController.ts
Normal file
157
server/src/controllers/v2/MonitorController.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import ApiError from "../../utils/ApiError.js";
|
||||
import MonitorService from "../../service/v2/business/MonitorService.js";
|
||||
import { MonitorType } from "../../db/v2/models/monitors/Monitor.js";
|
||||
class MonitorController {
|
||||
private monitorService: MonitorService;
|
||||
constructor(monitorService: MonitorService) {
|
||||
this.monitorService = monitorService;
|
||||
}
|
||||
|
||||
create = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
|
||||
const monitor = await this.monitorService.create(tokenizedUser, req.body);
|
||||
res.status(201).json({
|
||||
message: "Monitor created successfully",
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
get = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
throw new ApiError("Monitor ID is required", 400);
|
||||
}
|
||||
|
||||
const range = req.query.range;
|
||||
if (!range || typeof range !== "string") throw new ApiError("Range query parameter is required", 400);
|
||||
|
||||
let monitor;
|
||||
|
||||
const status = req.query.status;
|
||||
if (status && typeof status !== "string") {
|
||||
throw new ApiError("Status query parameter must be a string", 400);
|
||||
}
|
||||
|
||||
if (req.query.embedChecks === "true") {
|
||||
monitor = await this.monitorService.getEmbedChecks(id, range, status);
|
||||
} else {
|
||||
monitor = await this.monitorService.get(id);
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
message: "Monitor retrieved successfully",
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getAll = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
|
||||
let monitors;
|
||||
if (req.query.embedChecks === "true") {
|
||||
const page = Math.max(1, Number(req.query.page) || 1);
|
||||
const limit = Math.max(1, Number(req.query.limit) || 10);
|
||||
const type: MonitorType[] = req.query.type as MonitorType[];
|
||||
|
||||
monitors = await this.monitorService.getAllEmbedChecks(page, limit, type);
|
||||
} else {
|
||||
monitors = await this.monitorService.getAll();
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
message: "Monitors retrieved successfully",
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
toggleActive = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
throw new ApiError("Monitor ID is required", 400);
|
||||
}
|
||||
|
||||
const monitor = await this.monitorService.toggleActive(id, tokenizedUser);
|
||||
res.status(200).json({
|
||||
message: "Monitor paused/unpaused successfully",
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
update = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
throw new ApiError("Monitor ID is required", 400);
|
||||
}
|
||||
|
||||
const monitor = await this.monitorService.update(tokenizedUser, id, req.body);
|
||||
res.status(200).json({
|
||||
message: "Monitor updated successfully",
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
delete = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
throw new ApiError("Monitor ID is required", 400);
|
||||
}
|
||||
await this.monitorService.delete(id);
|
||||
|
||||
res.status(200).json({
|
||||
message: "Monitor deleted successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MonitorController;
|
||||
96
server/src/controllers/v2/NotificationChannelController.ts
Normal file
96
server/src/controllers/v2/NotificationChannelController.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import NotificationService from "../../service/v2/business/NotificationChannelService.js";
|
||||
|
||||
class NotificationChannelController {
|
||||
private notificationService: NotificationService;
|
||||
constructor(notificationService: NotificationService) {
|
||||
this.notificationService = notificationService;
|
||||
}
|
||||
|
||||
create = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
const channel = await this.notificationService.create(tokenizedUser, req.body);
|
||||
res.status(201).json({ message: "OK", data: channel });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getAll = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const notificationChannels = await this.notificationService.getAll();
|
||||
res.status(200).json({
|
||||
message: "OK",
|
||||
data: notificationChannels,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
toggleActive = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
return res.status(400).json({ message: "ID parameter is required" });
|
||||
}
|
||||
const notificationChannel = await this.notificationService.toggleActive(tokenizedUser, id);
|
||||
res.status(200).json({ message: "OK", data: notificationChannel });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
update = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const tokenizedUser = req.user;
|
||||
if (!tokenizedUser) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
return res.status(400).json({ message: "ID parameter is required" });
|
||||
}
|
||||
const updatedChannel = await this.notificationService.update(tokenizedUser, id, req.body);
|
||||
res.status(200).json({ message: "OK", data: updatedChannel });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
get = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
return res.status(400).json({ message: "ID parameter is required" });
|
||||
}
|
||||
const notificationChannel = await this.notificationService.get(id);
|
||||
res.status(200).json({ message: "OK", data: notificationChannel });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
delete = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const id = req.params.id;
|
||||
if (!id) {
|
||||
return res.status(400).json({ message: "ID parameter is required" });
|
||||
}
|
||||
await this.notificationService.delete(id);
|
||||
res.status(204).json({ message: "OK" });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default NotificationChannelController;
|
||||
29
server/src/controllers/v2/QueueController.ts
Normal file
29
server/src/controllers/v2/QueueController.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import QueueService from "../../service/v2/business/QueueService.js";
|
||||
class QueueController {
|
||||
private queueService: QueueService;
|
||||
constructor(queueService: QueueService) {
|
||||
this.queueService = queueService;
|
||||
}
|
||||
|
||||
getJobs = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const jobs = await this.queueService.getJobs();
|
||||
res.status(200).json({ message: "ok", data: jobs });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getMetrics = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const metrics = await this.queueService.getMetrics();
|
||||
res.status(200).json({ message: "ok", data: metrics });
|
||||
};
|
||||
|
||||
flush = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const result = await this.queueService.flush();
|
||||
res.status(200).json({ message: "ok", flushed: result });
|
||||
};
|
||||
}
|
||||
|
||||
export default QueueController;
|
||||
77
server/src/middleware/v2/VerifyPermissions.ts
Normal file
77
server/src/middleware/v2/VerifyPermissions.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import ApiError from "../../utils/ApiError.js";
|
||||
import { User, IUser, Role, IRole } from "../../db/v2/models/index.js";
|
||||
|
||||
const rolesCache = new Map<string, { roles: IRole[]; timestamp: number }>();
|
||||
// const CACHE_TTL = 30 * 60 * 1000; // 30 minutes
|
||||
const CACHE_TTL = 1; // 30 minutes
|
||||
const MAX_CACHE_SIZE = 1000;
|
||||
|
||||
const getCachedRoles = async (userId: string) => {
|
||||
if (rolesCache.size >= MAX_CACHE_SIZE) {
|
||||
const oldestKey = rolesCache.keys().next().value;
|
||||
if (!oldestKey) return null;
|
||||
rolesCache.delete(oldestKey);
|
||||
}
|
||||
|
||||
const cached = rolesCache.get(userId);
|
||||
|
||||
if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
|
||||
return cached.roles;
|
||||
}
|
||||
|
||||
const user: IUser | null = await User.findById(userId);
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const roles = await Role.find({ _id: { $in: user.roles } });
|
||||
rolesCache.set(userId, { roles, timestamp: Date.now() });
|
||||
return roles;
|
||||
};
|
||||
|
||||
const hasPermission = (roles: IRole[], requiredPermissions: string[]) => {
|
||||
const userPermissions = [...new Set(roles.flatMap((role) => role.permissions))];
|
||||
|
||||
if (userPermissions.includes("*")) return true;
|
||||
|
||||
const matches = (requiredPermission: string, userPermission: string) => {
|
||||
if (userPermission === requiredPermission) return true;
|
||||
if (userPermission.endsWith(".*")) {
|
||||
const prefix = userPermission.slice(0, -2);
|
||||
return requiredPermission.startsWith(prefix + ".");
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
return requiredPermissions.every((requiredPermission) => {
|
||||
return userPermissions.some((userPermission) => matches(requiredPermission, userPermission));
|
||||
});
|
||||
};
|
||||
|
||||
const verifyPermission = (resourceActions: string[]) => {
|
||||
return async (req: Request, res: Response, next: NextFunction) => {
|
||||
const tokenizedUser = req.user;
|
||||
|
||||
if (!tokenizedUser) {
|
||||
throw new ApiError("No user", 400);
|
||||
}
|
||||
|
||||
const userId = tokenizedUser.sub;
|
||||
if (!userId) {
|
||||
throw new ApiError("No user ID", 400);
|
||||
}
|
||||
|
||||
const userRoles = await getCachedRoles(userId);
|
||||
if (!userRoles) {
|
||||
throw new ApiError("User roles not found", 400);
|
||||
}
|
||||
const allowed = hasPermission(userRoles, resourceActions);
|
||||
if (!allowed) {
|
||||
throw new ApiError("Insufficient permissions", 403);
|
||||
}
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
export { verifyPermission };
|
||||
21
server/src/middleware/v2/VerifyToken.ts
Normal file
21
server/src/middleware/v2/VerifyToken.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { decode } from "../../utils/JWTUtils.js";
|
||||
import ApiError from "../../utils/ApiError.js";
|
||||
const verifyToken = (req: Request, res: Response, next: NextFunction) => {
|
||||
const token = req.cookies.token;
|
||||
|
||||
if (!token) {
|
||||
const error = new ApiError("No token provided", 401);
|
||||
return next(error);
|
||||
}
|
||||
|
||||
try {
|
||||
const decoded = decode(token);
|
||||
req.user = decoded;
|
||||
next();
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
export { verifyToken };
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Router } from "express";
|
||||
|
||||
import express from "express";
|
||||
import AuthController from "../../controllers/v2/AuthController.js";
|
||||
import { verifyToken } from "../../middleware/v2/VerifyToken.js";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
class AuthRoutes {
|
||||
private controller: AuthController;
|
||||
private router: Router;
|
||||
constructor(authController: AuthController) {
|
||||
this.controller = authController;
|
||||
this.router = Router();
|
||||
this.initRoutes();
|
||||
}
|
||||
|
||||
initRoutes = () => {
|
||||
this.router.post("/register", this.controller.register);
|
||||
this.router.post("/register/invite/:token", this.controller.registerWithInvite);
|
||||
this.router.post("/login", this.controller.login);
|
||||
this.router.post("/logout", this.controller.logout);
|
||||
this.router.get("/me", verifyToken, this.controller.me);
|
||||
this.router.post("/cleanup", this.controller.cleanup);
|
||||
this.router.post("/cleanup-monitors", this.controller.cleanMonitors);
|
||||
};
|
||||
|
||||
getRouter() {
|
||||
return this.router;
|
||||
}
|
||||
}
|
||||
|
||||
export default AuthRoutes;
|
||||
|
||||
30
server/src/routes/v2/invite.ts
Normal file
30
server/src/routes/v2/invite.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Router } from "express";
|
||||
import InviteController from "../../controllers/v2/InviteController.js";
|
||||
import { verifyToken } from "../../middleware/v2/VerifyToken.js";
|
||||
import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js";
|
||||
|
||||
class InviteRoutes {
|
||||
private router;
|
||||
private controller;
|
||||
constructor(inviteController: InviteController) {
|
||||
this.router = Router();
|
||||
this.controller = inviteController;
|
||||
this.initRoutes();
|
||||
}
|
||||
|
||||
initRoutes = () => {
|
||||
this.router.post("/", verifyToken, verifyPermission(["invite.create"]), this.controller.create);
|
||||
|
||||
this.router.get("/", verifyToken, verifyPermission(["invite.view"]), this.controller.getAll);
|
||||
|
||||
this.router.get("/:token", verifyToken, verifyPermission(["invite.view"]), this.controller.get);
|
||||
|
||||
this.router.delete("/:id", verifyToken, verifyPermission(["invite.delete"]), this.controller.delete);
|
||||
};
|
||||
|
||||
getRouter() {
|
||||
return this.router;
|
||||
}
|
||||
}
|
||||
|
||||
export default InviteRoutes;
|
||||
34
server/src/routes/v2/maintenance.ts
Normal file
34
server/src/routes/v2/maintenance.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Router } from "express";
|
||||
import MaintenanceController from "../../controllers/v2/MaintenanceController.js";
|
||||
import { verifyToken } from "../../middleware/v2/VerifyToken.js";
|
||||
import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js";
|
||||
|
||||
class MaintenanceRoutes {
|
||||
private router;
|
||||
private controller;
|
||||
constructor(maintenanceController: MaintenanceController) {
|
||||
this.router = Router();
|
||||
this.controller = maintenanceController;
|
||||
this.initRoutes();
|
||||
}
|
||||
|
||||
initRoutes = () => {
|
||||
this.router.post("/", verifyToken, verifyPermission(["maintenance.create"]), this.controller.create);
|
||||
|
||||
this.router.get("/", verifyToken, verifyPermission(["maintenance.view"]), this.controller.getAll);
|
||||
|
||||
this.router.patch("/:id/active", verifyToken, verifyPermission(["maintenance.update"]), this.controller.toggleActive);
|
||||
|
||||
this.router.patch("/:id", verifyToken, verifyPermission(["maintenance.update"]), this.controller.update);
|
||||
|
||||
this.router.get("/:id", verifyToken, verifyPermission(["maintenance.view"]), this.controller.get);
|
||||
|
||||
this.router.delete("/:id", verifyToken, verifyPermission(["maintenance.delete"]), this.controller.delete);
|
||||
};
|
||||
|
||||
getRouter() {
|
||||
return this.router;
|
||||
}
|
||||
}
|
||||
|
||||
export default MaintenanceRoutes;
|
||||
34
server/src/routes/v2/monitors.ts
Normal file
34
server/src/routes/v2/monitors.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Router } from "express";
|
||||
import MonitorController from "../../controllers/v2/MonitorController.js";
|
||||
import { verifyToken } from "../../middleware/v2/VerifyToken.js";
|
||||
import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js";
|
||||
|
||||
class MonitorRoutes {
|
||||
private router;
|
||||
private controller;
|
||||
constructor(monitorController: MonitorController) {
|
||||
this.router = Router();
|
||||
this.controller = monitorController;
|
||||
this.initRoutes();
|
||||
}
|
||||
|
||||
initRoutes = () => {
|
||||
this.router.post("/", verifyToken, verifyPermission(["monitors.create"]), this.controller.create);
|
||||
|
||||
this.router.get("/", verifyToken, verifyPermission(["monitors.view"]), this.controller.getAll);
|
||||
|
||||
this.router.patch("/:id/active", verifyToken, verifyPermission(["monitors.update"]), this.controller.toggleActive);
|
||||
|
||||
this.router.patch("/:id", verifyToken, verifyPermission(["monitors.update"]), this.controller.update);
|
||||
|
||||
this.router.get("/:id", verifyToken, verifyPermission(["monitors.view"]), this.controller.get);
|
||||
|
||||
this.router.delete("/:id", verifyToken, verifyPermission(["monitors.delete"]), this.controller.delete);
|
||||
};
|
||||
|
||||
getRouter() {
|
||||
return this.router;
|
||||
}
|
||||
}
|
||||
|
||||
export default MonitorRoutes;
|
||||
34
server/src/routes/v2/notificationChannels.ts
Normal file
34
server/src/routes/v2/notificationChannels.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Router } from "express";
|
||||
import NotificationController from "../../controllers/v2/NotificationChannelController.js";
|
||||
import { verifyToken } from "../../middleware/v2/VerifyToken.js";
|
||||
import { verifyPermission } from "../../middleware/v2/VerifyPermissions.js";
|
||||
|
||||
class NotificationChannelRoutes {
|
||||
private router;
|
||||
private controller;
|
||||
constructor(notificationController: NotificationController) {
|
||||
this.router = Router();
|
||||
this.controller = notificationController;
|
||||
this.initRoutes();
|
||||
}
|
||||
|
||||
initRoutes = () => {
|
||||
this.router.post("/", verifyToken, verifyPermission(["notifications.create"]), this.controller.create);
|
||||
|
||||
this.router.get("/", verifyToken, verifyPermission(["notifications.view"]), this.controller.getAll);
|
||||
|
||||
this.router.patch("/:id/active", verifyToken, verifyPermission(["notifications.update"]), this.controller.toggleActive);
|
||||
|
||||
this.router.patch("/:id", verifyToken, verifyPermission(["notifications.update"]), this.controller.update);
|
||||
|
||||
this.router.get("/:id", verifyToken, verifyPermission(["notifications.view"]), this.controller.get);
|
||||
|
||||
this.router.delete("/:id", verifyToken, verifyPermission(["notifications.delete"]), this.controller.delete);
|
||||
};
|
||||
|
||||
getRouter() {
|
||||
return this.router;
|
||||
}
|
||||
}
|
||||
|
||||
export default NotificationChannelRoutes;
|
||||
24
server/src/routes/v2/queue.ts
Normal file
24
server/src/routes/v2/queue.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import QueueController from "../../controllers/v2/QueueController.js";
|
||||
import { Router } from "express";
|
||||
|
||||
class QueueRoutes {
|
||||
private router;
|
||||
private controller;
|
||||
constructor(queueController: QueueController) {
|
||||
this.router = Router();
|
||||
this.controller = queueController;
|
||||
this.initRoutes();
|
||||
}
|
||||
|
||||
initRoutes() {
|
||||
this.router.get("/jobs", this.controller.getJobs);
|
||||
this.router.get("/metrics", this.controller.getMetrics);
|
||||
this.router.post("/flush", this.controller.flush);
|
||||
}
|
||||
|
||||
getRouter() {
|
||||
return this.router;
|
||||
}
|
||||
}
|
||||
|
||||
export default QueueRoutes;
|
||||
@@ -4,6 +4,8 @@ import ApiError from "../../../utils/ApiError.js";
|
||||
import { Types } from "mongoose";
|
||||
import { IJobQueue } from "../infrastructure/JobQueue.js";
|
||||
|
||||
const SERVICE_NAME = "AuthServiceV2";
|
||||
|
||||
export const PERMISSIONS = {
|
||||
users: {
|
||||
all: "users.*",
|
||||
@@ -94,6 +96,8 @@ export interface IAuthService {
|
||||
}
|
||||
|
||||
class AuthService implements IAuthService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private jobQueue: IJobQueue;
|
||||
constructor(jobQueue: IJobQueue) {
|
||||
this.jobQueue = jobQueue;
|
||||
|
||||
@@ -6,12 +6,15 @@ import { StatusResponse } from "../infrastructure/NetworkService.js";
|
||||
import type { ICapturePayload, ILighthousePayload } from "../infrastructure/NetworkService.js";
|
||||
import mongoose from "mongoose";
|
||||
|
||||
const SERVICE_NAME = "CheckServiceV2";
|
||||
export interface ICheckService {
|
||||
buildCheck: (statusResponse: StatusResponse, type: MonitorType) => Promise<ICheck>;
|
||||
cleanupOrphanedChecks: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
class CheckService implements ICheckService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private isCapturePayload = (payload: any): payload is ICapturePayload => {
|
||||
if (!payload || typeof payload !== "object") return false;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import crypto from "node:crypto";
|
||||
import { ITokenizedUser, IInvite, Invite } from "../../../db/v2/models/index.js";
|
||||
import ApiError from "../../../utils/ApiError.js";
|
||||
|
||||
const SERVICE_NAME = "InviteServiceV2";
|
||||
export interface IInviteService {
|
||||
create: (tokenizedUser: ITokenizedUser, invite: IInvite) => Promise<{ token: string }>;
|
||||
getAll: () => Promise<IInvite[]>;
|
||||
@@ -10,6 +11,7 @@ export interface IInviteService {
|
||||
}
|
||||
|
||||
class InviteService implements IInviteService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor() {}
|
||||
|
||||
create = async (tokenizedUser: ITokenizedUser, inviteData: IInvite) => {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ITokenizedUser, IMaintenance, Maintenance } from "../../../db/v2/models/index.js";
|
||||
import ApiError from "../../../utils/ApiError.js";
|
||||
|
||||
const SERVICE_NAME = "MaintenanceServiceV2";
|
||||
|
||||
export interface IMaintenanceService {
|
||||
create: (
|
||||
tokenizedUser: ITokenizedUser,
|
||||
@@ -18,6 +20,7 @@ export interface IMaintenanceService {
|
||||
type MaintenanceCache = Map<string, IMaintenance[]>;
|
||||
|
||||
class MaintenanceService implements IMaintenanceService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private maintenanceCache: MaintenanceCache;
|
||||
private lastRefresh: number;
|
||||
private CACHE_TTL_MS = 60 * 1000;
|
||||
|
||||
468
server/src/service/v2/business/MonitorService.ts
Normal file
468
server/src/service/v2/business/MonitorService.ts
Normal file
@@ -0,0 +1,468 @@
|
||||
import mongoose from "mongoose";
|
||||
|
||||
import { IMonitor, Monitor, ITokenizedUser, MonitorStats, Check } from "../../../db/v2/models/index.js";
|
||||
import ApiError from "../../../utils/ApiError.js";
|
||||
import { IJobQueue } from "../infrastructure/JobQueue.js";
|
||||
import { MonitorWithChecksResponse } from "../../../types/index.js";
|
||||
import { MonitorStatus, MonitorType } from "../../../db/v2/models/monitors/Monitor.js";
|
||||
|
||||
const SERVICE_NAME = "MonitorServiceV2";
|
||||
|
||||
export interface IMonitorService {
|
||||
create: (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => Promise<IMonitor>;
|
||||
getAll: () => Promise<IMonitor[]>;
|
||||
getAllEmbedChecks: (page: number, limit: number, type: MonitorType[]) => Promise<any[]>;
|
||||
get: (monitorId: string) => Promise<IMonitor>;
|
||||
getEmbedChecks: (monitorId: string, range: string, status?: string) => Promise<MonitorWithChecksResponse>;
|
||||
toggleActive: (monitorId: string, tokenizedUser: ITokenizedUser) => Promise<IMonitor>;
|
||||
update: (tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial<IMonitor>) => Promise<IMonitor>;
|
||||
delete: (monitorId: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
class MonitorService implements IMonitorService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private jobQueue: IJobQueue;
|
||||
constructor(jobQueue: IJobQueue) {
|
||||
this.jobQueue = jobQueue;
|
||||
}
|
||||
|
||||
create = async (tokenizedUser: ITokenizedUser, monitorData: IMonitor) => {
|
||||
const monitor = await Monitor.create({
|
||||
...monitorData,
|
||||
createdBy: tokenizedUser.sub,
|
||||
updatedBy: tokenizedUser.sub,
|
||||
});
|
||||
await MonitorStats.create({
|
||||
monitorId: monitor._id,
|
||||
currentStreakStartedAt: Date.now(),
|
||||
});
|
||||
await this.jobQueue.addJob(monitor);
|
||||
return monitor;
|
||||
};
|
||||
|
||||
getAll = async () => {
|
||||
return Monitor.find();
|
||||
};
|
||||
|
||||
getAllEmbedChecks = async (page: number, limit: number, type: MonitorType[] = []) => {
|
||||
const skip = (page - 1) * limit;
|
||||
let find = {};
|
||||
if (type.length > 0) find = { type: { $in: type } };
|
||||
const monitors = await Monitor.find(find).skip(skip).limit(limit);
|
||||
return monitors;
|
||||
};
|
||||
|
||||
get = async (monitorId: string) => {
|
||||
const monitor = await Monitor.findById(monitorId);
|
||||
if (!monitor) {
|
||||
throw new ApiError("Monitor not found", 404);
|
||||
}
|
||||
return monitor;
|
||||
};
|
||||
|
||||
private getStartDate(range: string): Date {
|
||||
const now = new Date();
|
||||
switch (range) {
|
||||
case "30m":
|
||||
return new Date(now.getTime() - 30 * 60 * 1000);
|
||||
case "24h":
|
||||
return new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
||||
case "7d":
|
||||
return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
case "30d":
|
||||
return new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
||||
default:
|
||||
throw new ApiError("Invalid range parameter", 400);
|
||||
}
|
||||
}
|
||||
|
||||
private getDateFormat(range: string): string {
|
||||
switch (range) {
|
||||
case "30m":
|
||||
return "%Y-%m-%dT%H:%M:00Z";
|
||||
case "24h":
|
||||
case "7d":
|
||||
return "%Y-%m-%dT%H:00:00Z";
|
||||
case "30d":
|
||||
return "%Y-%m-%d";
|
||||
default:
|
||||
throw new ApiError("Invalid range parameter", 400);
|
||||
}
|
||||
}
|
||||
|
||||
private getBaseGroup = (dateFormat: string): Record<string, any> => {
|
||||
return {
|
||||
_id: { $dateToString: { format: dateFormat, date: "$createdAt" } },
|
||||
count: { $sum: 1 },
|
||||
avgResponseTime: { $avg: "$responseTime" },
|
||||
};
|
||||
};
|
||||
|
||||
private getBaseProjection = (): object => {
|
||||
return { status: 1, responseTime: 1, createdAt: 1 };
|
||||
};
|
||||
|
||||
private getPageSpeedGroup = (dateFormat: string): Record<string, any> => {
|
||||
return {
|
||||
_id: { $dateToString: { format: dateFormat, date: "$createdAt" } },
|
||||
count: { $sum: 1 },
|
||||
avgResponseTime: { $avg: "$responseTime" },
|
||||
accessibility: { $avg: "$lighthouse.accessibility" },
|
||||
bestPractices: { $avg: "$lighthouse.bestPractices" },
|
||||
seo: { $avg: "$lighthouse.seo" },
|
||||
performance: { $avg: "$lighthouse.performance" },
|
||||
cls: { $avg: "$lighthouse.audits.cls.score" },
|
||||
si: { $avg: "$lighthouse.audits.si.score" },
|
||||
fcp: { $avg: "$lighthouse.audits.fcp.score" },
|
||||
lcp: { $avg: "$lighthouse.audits.lcp.score" },
|
||||
tbt: { $avg: "$lighthouse.audits.tbt.score" },
|
||||
};
|
||||
};
|
||||
|
||||
private getPageSpeedProjection = (): object => {
|
||||
const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 };
|
||||
projectStage["lighthouse.accessibility"] = 1;
|
||||
projectStage["lighthouse.seo"] = 1;
|
||||
projectStage["lighthouse.bestPractices"] = 1;
|
||||
projectStage["lighthouse.performance"] = 1;
|
||||
projectStage["lighthouse.audits.cls.score"] = 1;
|
||||
projectStage["lighthouse.audits.si.score"] = 1;
|
||||
projectStage["lighthouse.audits.fcp.score"] = 1;
|
||||
projectStage["lighthouse.audits.lcp.score"] = 1;
|
||||
projectStage["lighthouse.audits.tbt.score"] = 1;
|
||||
return projectStage;
|
||||
};
|
||||
|
||||
private getInfraGroup = (dateFormat: string): Record<string, any> => {
|
||||
return {
|
||||
_id: { $dateToString: { format: dateFormat, date: "$createdAt" } },
|
||||
count: { $sum: 1 },
|
||||
avgResponseTime: { $avg: "$responseTime" },
|
||||
physicalCores: { $last: "$system.cpu.physical_core" },
|
||||
logicalCores: { $last: "$system.cpu.logical_core" },
|
||||
frequency: { $avg: "$system.cpu.frequency" },
|
||||
currentFrequency: { $last: "$system.cpu.current_frequency" },
|
||||
tempsArrays: { $push: "$system.cpu.temperature" },
|
||||
freePercent: { $avg: "$system.cpu.free_percent" },
|
||||
usedPercent: { $avg: "$system.cpu.usage_percent" },
|
||||
total_bytes: { $last: "$system.memory.total_bytes" },
|
||||
available_bytes: { $last: "$system.memory.available_bytes" },
|
||||
used_bytes: { $last: "$system.memory.used_bytes" },
|
||||
memory_usage_percent: { $avg: "$system.memory.usage_percent" },
|
||||
disksArray: { $push: "$system.disk" },
|
||||
os: { $last: "$system.host.os" },
|
||||
platform: { $last: "$system.host.platform" },
|
||||
kernel_version: { $last: "$system.host.kernel_version" },
|
||||
pretty_name: { $last: "$system.host.pretty_name" },
|
||||
netsArray: { $push: "$system.net" },
|
||||
};
|
||||
};
|
||||
|
||||
private getInfraProjection = (): object => {
|
||||
const projectStage: any = { status: 1, responseTime: 1, createdAt: 1 };
|
||||
projectStage["system.cpu.physical_core"] = 1;
|
||||
projectStage["system.cpu.logical_core"] = 1;
|
||||
projectStage["system.cpu.frequency"] = 1;
|
||||
projectStage["system.cpu.current_frequency"] = 1;
|
||||
projectStage["system.cpu.temperature"] = 1;
|
||||
projectStage["system.cpu.free_percent"] = 1;
|
||||
projectStage["system.cpu.usage_percent"] = 1;
|
||||
projectStage["system.memory.total_bytes"] = 1;
|
||||
projectStage["system.memory.available_bytes"] = 1;
|
||||
projectStage["system.memory.used_bytes"] = 1;
|
||||
projectStage["system.memory.usage_percent"] = 1;
|
||||
projectStage["system.disk"] = 1;
|
||||
projectStage["system.host.os"] = 1;
|
||||
projectStage["system.host.platform"] = 1;
|
||||
projectStage["system.host.kernel_version"] = 1;
|
||||
projectStage["system.host.pretty_name"] = 1;
|
||||
projectStage["system.net"] = 1;
|
||||
return projectStage;
|
||||
};
|
||||
|
||||
private getFinalProjection = (type: string): object => {
|
||||
if (type === "pagespeed") {
|
||||
return {
|
||||
_id: 1,
|
||||
count: 1,
|
||||
avgResponseTime: 1,
|
||||
accessibility: "$accessibility",
|
||||
seo: "$seo",
|
||||
bestPractices: "$bestPractices",
|
||||
performance: "$performance",
|
||||
cls: "$cls",
|
||||
si: "$si",
|
||||
fcp: "$fcp",
|
||||
lcp: "$lcp",
|
||||
tbt: "$tbt",
|
||||
};
|
||||
}
|
||||
|
||||
if (type === "infrastructure") {
|
||||
return {
|
||||
_id: 1,
|
||||
count: 1,
|
||||
avgResponseTime: 1,
|
||||
cpu: {
|
||||
physicalCores: "$physicalCores",
|
||||
logicalCores: "$logicalCores",
|
||||
frequency: "$frequency",
|
||||
currentFrequency: "$currentFrequency",
|
||||
temperatures: {
|
||||
$map: {
|
||||
input: {
|
||||
$range: [0, { $size: { $arrayElemAt: ["$tempsArrays", 0] } }],
|
||||
},
|
||||
as: "idx",
|
||||
in: {
|
||||
$avg: {
|
||||
$map: {
|
||||
input: "$tempsArrays",
|
||||
as: "arr",
|
||||
in: { $arrayElemAt: ["$$arr", "$$idx"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
freePercent: "$freePercent",
|
||||
usedPercent: "$usedPercent",
|
||||
},
|
||||
memory: {
|
||||
total_bytes: "$total_bytes",
|
||||
available_bytes: "$available_bytes",
|
||||
used_bytes: "$used_bytes",
|
||||
usage_percent: "$memory_usage_percent",
|
||||
},
|
||||
disks: {
|
||||
$map: {
|
||||
input: {
|
||||
$range: [0, { $size: { $arrayElemAt: ["$disksArray", 0] } }],
|
||||
},
|
||||
as: "idx",
|
||||
in: {
|
||||
$let: {
|
||||
vars: {
|
||||
diskGroup: {
|
||||
$map: {
|
||||
input: "$disksArray",
|
||||
as: "diskArr",
|
||||
in: { $arrayElemAt: ["$$diskArr", "$$idx"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
in: {
|
||||
device: { $arrayElemAt: ["$$diskGroup.device", 0] },
|
||||
total_bytes: { $avg: "$$diskGroup.total_bytes" },
|
||||
free_bytes: { $avg: "$$diskGroup.free_bytes" },
|
||||
used_bytes: { $avg: "$$diskGroup.used_bytes" },
|
||||
usage_percent: { $avg: "$$diskGroup.usage_percent" },
|
||||
total_inodes: { $avg: "$$diskGroup.total_inodes" },
|
||||
free_inodes: { $avg: "$$diskGroup.free_inodes" },
|
||||
used_inodes: { $avg: "$$diskGroup.used_inodes" },
|
||||
inodes_usage_percent: {
|
||||
$avg: "$$diskGroup.inodes_usage_percent",
|
||||
},
|
||||
read_bytes: { $avg: "$$diskGroup.read_bytes" },
|
||||
write_bytes: { $avg: "$$diskGroup.write_bytes" },
|
||||
read_time: { $avg: "$$diskGroup.read_time" },
|
||||
write_time: { $avg: "$$diskGroup.write_time" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
host: {
|
||||
os: "$os",
|
||||
platform: "$platform",
|
||||
kernel_version: "$kernel_version",
|
||||
pretty_name: "$pretty_name",
|
||||
},
|
||||
net: {
|
||||
$map: {
|
||||
input: {
|
||||
$range: [0, { $size: { $arrayElemAt: ["$netsArray", 0] } }],
|
||||
},
|
||||
as: "idx",
|
||||
in: {
|
||||
$let: {
|
||||
vars: {
|
||||
netGroup: {
|
||||
$map: {
|
||||
input: "$netsArray",
|
||||
as: "netArr",
|
||||
in: { $arrayElemAt: ["$$netArr", "$$idx"] },
|
||||
},
|
||||
},
|
||||
},
|
||||
in: {
|
||||
name: { $arrayElemAt: ["$$netGroup.name", 0] },
|
||||
bytes_sent: { $avg: "$$netGroup.bytes_sent" },
|
||||
bytes_recv: { $avg: "$$netGroup.bytes_recv" },
|
||||
packets_sent: { $avg: "$$netGroup.packets_sent" },
|
||||
packets_recv: { $avg: "$$netGroup.packets_recv" },
|
||||
err_in: { $avg: "$$netGroup.err_in" },
|
||||
err_out: { $avg: "$$netGroup.err_out" },
|
||||
drop_in: { $avg: "$$netGroup.drop_in" },
|
||||
drop_out: { $avg: "$$netGroup.drop_out" },
|
||||
fifo_in: { $avg: "$$netGroup.fifo_in" },
|
||||
fifo_out: { $avg: "$$netGroup.fifo_out" },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
getEmbedChecks = async (monitorId: string, range: string, status: string | undefined): Promise<MonitorWithChecksResponse> => {
|
||||
const monitor = await Monitor.findById(monitorId);
|
||||
if (!monitor) {
|
||||
throw new ApiError("Monitor not found", 404);
|
||||
}
|
||||
const startDate = this.getStartDate(range);
|
||||
const dateFormat = this.getDateFormat(range);
|
||||
|
||||
// Build match stage
|
||||
const matchStage: {
|
||||
monitorId: mongoose.Types.ObjectId;
|
||||
createdAt: { $gte: Date };
|
||||
status?: string;
|
||||
} = {
|
||||
monitorId: monitor._id,
|
||||
createdAt: { $gte: startDate },
|
||||
};
|
||||
|
||||
if (status) {
|
||||
matchStage.status = status;
|
||||
}
|
||||
|
||||
let groupClause;
|
||||
|
||||
if (monitor.type === "pagespeed") {
|
||||
groupClause = this.getPageSpeedGroup(dateFormat);
|
||||
} else if (monitor.type === "infrastructure") {
|
||||
groupClause = this.getInfraGroup(dateFormat);
|
||||
} else {
|
||||
groupClause = this.getBaseGroup(dateFormat);
|
||||
}
|
||||
|
||||
let projectStage;
|
||||
if (monitor.type === "pagespeed") {
|
||||
projectStage = this.getPageSpeedProjection();
|
||||
} else if (monitor.type === "infrastructure") {
|
||||
projectStage = this.getInfraProjection();
|
||||
} else {
|
||||
projectStage = this.getBaseProjection();
|
||||
}
|
||||
|
||||
let finalProjection = {};
|
||||
if (monitor.type === "pagespeed" || monitor.type === "infrastructure") {
|
||||
finalProjection = this.getFinalProjection(monitor.type);
|
||||
} else {
|
||||
finalProjection = { _id: 1, count: 1, avgResponseTime: 1 };
|
||||
}
|
||||
|
||||
const checks = await Check.aggregate([
|
||||
{
|
||||
$match: matchStage,
|
||||
},
|
||||
{ $sort: { createdAt: 1 } },
|
||||
{ $project: projectStage },
|
||||
{ $group: groupClause },
|
||||
{ $sort: { _id: -1 } },
|
||||
{
|
||||
$project: finalProjection,
|
||||
},
|
||||
]);
|
||||
|
||||
// Get monitor stats
|
||||
const monitorStats = await MonitorStats.findOne({
|
||||
monitorId: monitor._id,
|
||||
}).lean();
|
||||
|
||||
if (!monitorStats) {
|
||||
throw new ApiError("Monitor stats not found", 404);
|
||||
}
|
||||
|
||||
return {
|
||||
monitor: monitor.toObject(),
|
||||
checks,
|
||||
stats: monitorStats,
|
||||
};
|
||||
};
|
||||
|
||||
async toggleActive(id: string, tokenizedUser: ITokenizedUser) {
|
||||
const pendingStatus: MonitorStatus = "initializing";
|
||||
const updatedMonitor = await Monitor.findOneAndUpdate(
|
||||
{ _id: id },
|
||||
[
|
||||
{
|
||||
$set: {
|
||||
isActive: { $not: "$isActive" },
|
||||
status: pendingStatus,
|
||||
updatedBy: tokenizedUser.sub,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
},
|
||||
],
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
if (!updatedMonitor) {
|
||||
throw new ApiError("Monitor not found", 404);
|
||||
}
|
||||
|
||||
await this.jobQueue.updateJob(updatedMonitor);
|
||||
|
||||
if (updatedMonitor?.isActive) {
|
||||
await this.jobQueue.resumeJob(updatedMonitor);
|
||||
} else {
|
||||
await this.jobQueue.pauseJob(updatedMonitor);
|
||||
}
|
||||
return updatedMonitor;
|
||||
}
|
||||
|
||||
async update(tokenizedUser: ITokenizedUser, monitorId: string, updateData: Partial<IMonitor>) {
|
||||
const allowedFields: (keyof IMonitor)[] = ["name", "interval", "isActive", "n", "notificationChannels"];
|
||||
const safeUpdate: Partial<IMonitor> = {};
|
||||
|
||||
for (const field of allowedFields) {
|
||||
if (updateData[field] !== undefined) {
|
||||
(safeUpdate as any)[field] = updateData[field];
|
||||
}
|
||||
}
|
||||
|
||||
const updatedMonitor = await Monitor.findByIdAndUpdate(
|
||||
monitorId,
|
||||
{
|
||||
$set: {
|
||||
...safeUpdate,
|
||||
updatedAt: new Date(),
|
||||
updatedBy: tokenizedUser.sub,
|
||||
},
|
||||
},
|
||||
{ new: true, runValidators: true }
|
||||
);
|
||||
|
||||
if (!updatedMonitor) {
|
||||
throw new ApiError("Monitor not found", 404);
|
||||
}
|
||||
await this.jobQueue.updateJob(updatedMonitor);
|
||||
return updatedMonitor;
|
||||
}
|
||||
|
||||
async delete(monitorId: string) {
|
||||
const monitor = await Monitor.findById(monitorId);
|
||||
if (!monitor) {
|
||||
throw new ApiError("Monitor not found", 404);
|
||||
}
|
||||
await monitor.deleteOne();
|
||||
await this.jobQueue.deleteJob(monitor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default MonitorService;
|
||||
@@ -1,10 +1,13 @@
|
||||
import { MonitorStats } from "../../../db/v2/models/index.js";
|
||||
import { Monitor } from "../../../db/v2/models/index.js";
|
||||
|
||||
const SERVICE_NAME = "MonitorStatsServiceV2";
|
||||
export interface IMonitorStatsService {
|
||||
cleanupOrphanedMonitorStats: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
class MonitorStatsService implements IMonitorStatsService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor() {}
|
||||
|
||||
async cleanupOrphanedMonitorStats() {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ITokenizedUser, INotificationChannel, NotificationChannel, Monitor } from "../../../db/v2/models/index.js";
|
||||
import ApiError from "../../../utils/ApiError.js";
|
||||
|
||||
const SERVICE_NAME = "NotificationChannelServiceV2";
|
||||
|
||||
export interface INotificationChannelService {
|
||||
create: (
|
||||
tokenizedUser: ITokenizedUser,
|
||||
@@ -15,6 +17,8 @@ export interface INotificationChannelService {
|
||||
}
|
||||
|
||||
class NotificationChannelService implements INotificationChannelService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor() {}
|
||||
|
||||
create = async (tokenizedUser: ITokenizedUser, notificationChannelData: INotificationChannel) => {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { IJobQueue } from "../infrastructure/JobQueue.js";
|
||||
|
||||
const SERVICE_NAME = "QueueServiceV2";
|
||||
|
||||
class QueueService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private jobQueue: IJobQueue;
|
||||
|
||||
constructor(jobQueue: IJobQueue) {
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { IUser, User } from "../../../db/v2/models/index.js";
|
||||
|
||||
const SERVICE_NAME = "UserServiceV2";
|
||||
export interface IUserService {
|
||||
getAllUsers(): Promise<IUser[]>;
|
||||
}
|
||||
|
||||
class UserService implements IUserService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
async getAllUsers(): Promise<IUser[]> {
|
||||
return await User.find();
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ import { INotificationService } from "./NotificationService.js";
|
||||
import { IMaintenanceService } from "../business/MaintenanceService.js";
|
||||
import ApiError from "../../../utils/ApiError.js";
|
||||
|
||||
const SERVICE_NAME = "JobGeneratorV2";
|
||||
export interface IJobGenerator {
|
||||
generateJob: () => (Monitor: IMonitor) => Promise<void>;
|
||||
generateCleanupJob: () => () => Promise<void>;
|
||||
}
|
||||
|
||||
class JobGenerator implements IJobGenerator {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private networkService: INetworkService;
|
||||
private checkService: ICheckService;
|
||||
private monitorStatsService: IMonitorStatsService;
|
||||
|
||||
@@ -2,6 +2,8 @@ import { IJob } from "super-simple-scheduler/dist/job/job.js";
|
||||
import { Monitor, IMonitor } from "../../../db/v2/models/index.js";
|
||||
import Scheduler from "super-simple-scheduler";
|
||||
import { IJobGenerator } from "./JobGenerator.js";
|
||||
|
||||
const SERVICE_NAME = "JobQueueV2";
|
||||
export interface IJobMetrics {
|
||||
jobs: number;
|
||||
activeJobs: number;
|
||||
@@ -36,6 +38,8 @@ export interface IJobQueue {
|
||||
}
|
||||
|
||||
export default class JobQueue implements IJobQueue {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private scheduler: Scheduler;
|
||||
private static instance: JobQueue | null = null;
|
||||
private jobGenerator: any;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Got, HTTPError } from "got";
|
||||
import got from "got";
|
||||
import ping from "ping";
|
||||
import { IMonitor } from "../../../db/v2/models/index.js";
|
||||
import { GotTimings } from "../../../db/v2/models/checks/Check.js";
|
||||
@@ -7,6 +8,8 @@ import type { ISystemInfo, ICaptureInfo, ILighthouseResult } from "../../../db/v
|
||||
import { MonitorType, MonitorStatus } from "../../../db/v2/models/monitors/Monitor.js";
|
||||
import ApiError from "../../../utils/ApiError.js";
|
||||
import { config } from "../../../config/index.js";
|
||||
|
||||
const SERVICE_NAME = "NetworkServiceV2";
|
||||
export interface INetworkService {
|
||||
requestHttp: (monitor: IMonitor) => Promise<StatusResponse>;
|
||||
requestInfrastructure: (monitor: IMonitor) => Promise<StatusResponse>;
|
||||
@@ -36,9 +39,10 @@ export interface StatusResponse<TPayload = unknown> {
|
||||
}
|
||||
|
||||
class NetworkService implements INetworkService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private got: Got;
|
||||
private NETWORK_ERROR: number;
|
||||
constructor(got: Got) {
|
||||
constructor() {
|
||||
this.got = got;
|
||||
this.NETWORK_ERROR = 5000;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import UserService from "../business/UserService.js";
|
||||
import { IMonitor, NotificationChannel } from "../../../db/v2/models/index.js";
|
||||
import { EmailService, SlackService, DiscordService, WebhookService } from "./NotificationServices/index.js";
|
||||
|
||||
const SERVICE_NAME = "NotificationServiceV2";
|
||||
export interface INotificationService {
|
||||
handleNotifications: (monitor: IMonitor) => Promise<void>;
|
||||
}
|
||||
|
||||
class NotificationService implements INotificationService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private emailService: EmailService;
|
||||
private slackService: SlackService;
|
||||
private discordService: DiscordService;
|
||||
|
||||
@@ -2,7 +2,10 @@ import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.j
|
||||
import { IAlert, IMessageService } from "./IMessageService.js";
|
||||
import got from "got";
|
||||
import ApiError from "../../../../utils/ApiError.js";
|
||||
|
||||
const SERVICE_NAME = "DiscordServiceV2";
|
||||
class DiscordService implements IMessageService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor() {}
|
||||
|
||||
private toDiscordEmbeds = (alert: IAlert) => {
|
||||
|
||||
@@ -4,7 +4,10 @@ import nodemailer, { Transporter } from "nodemailer";
|
||||
import { config } from "../../../../config/index.js";
|
||||
import UserService from "../../business/UserService.js";
|
||||
import ApiError from "../../../../utils/ApiError.js";
|
||||
|
||||
const SERVICE_NAME = "EmailServiceV2";
|
||||
class EmailService implements IMessageService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private transporter: Transporter;
|
||||
private userService: UserService;
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.j
|
||||
import { IAlert, IMessageService } from "./IMessageService.js";
|
||||
import got from "got";
|
||||
|
||||
const SERVICE_NAME = "SlackServiceV2";
|
||||
class SlackService implements IMessageService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor() {}
|
||||
|
||||
private toSlackBlocks = (alert: IAlert) => {
|
||||
|
||||
@@ -2,7 +2,11 @@ import { IMonitor, INotificationChannel } from "../../../../db/v2/models/index.j
|
||||
import { IAlert, IMessageService } from "./IMessageService.js";
|
||||
import ApiError from "../../../../utils/ApiError.js";
|
||||
import got from "got";
|
||||
|
||||
const SERVICE_NAME = "WebhookServiceV2";
|
||||
class WebhookService implements IMessageService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
constructor() {}
|
||||
|
||||
buildAlert = (monitor: IMonitor) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { IMonitor, IMonitorStats, MonitorStats } from "../../../db/v2/models/ind
|
||||
import { StatusResponse } from "./NetworkService.js";
|
||||
import ApiError from "../../../utils/ApiError.js";
|
||||
|
||||
const SERVICE_NAME = "StatusServiceV2";
|
||||
const MAX_LATEST_CHECKS = 25;
|
||||
export interface IStatusService {
|
||||
updateMonitorStatus: (monitor: IMonitor, status: StatusResponse) => Promise<StatusChangeResult>;
|
||||
@@ -14,6 +15,7 @@ export interface IStatusService {
|
||||
export type StatusChangeResult = [updatedMonitor: IMonitor, statusChanged: boolean];
|
||||
|
||||
class StatusService implements IStatusService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
updateMonitorStatus = async (monitor: IMonitor, statusResponse: StatusResponse): Promise<StatusChangeResult> => {
|
||||
const newStatus = statusResponse.status;
|
||||
monitor.lastCheckedAt = new Date();
|
||||
|
||||
10
server/src/types/express.d.ts
vendored
Normal file
10
server/src/types/express.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import { ITokenizedUser } from "../db/models/index.ts";
|
||||
|
||||
declare global {
|
||||
namespace Express {
|
||||
interface Request {
|
||||
user?: ITokenizedUser;
|
||||
resource?: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
server/src/types/index.ts
Normal file
1
server/src/types/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type { MonitorWithChecksResponse } from "./monitor-response-with-checks.js";
|
||||
11
server/src/types/monitor-response-with-checks.ts
Normal file
11
server/src/types/monitor-response-with-checks.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { IMonitor, IMonitorStats } from "../db/v2/models/index.js";
|
||||
|
||||
export interface MonitorWithChecksResponse {
|
||||
monitor: IMonitor;
|
||||
checks: Array<{
|
||||
_id: string;
|
||||
count: number;
|
||||
avgResponseTime: number;
|
||||
}>;
|
||||
stats: IMonitorStats;
|
||||
}
|
||||
Reference in New Issue
Block a user