reorg to domain and api
This commit is contained in:
1 parent
0fb210e9e1
commit
d022f7d849
190 files changed
+643
-830
No files matched your search
Executable
+291
@@ -0,0 +1,291 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { requireTeamId, requireUserEmail, requireUserId, requireUserRoles } from "@/api/controllers/controllerUtils.js";
|
||||
|
||||
import {
|
||||
registrationBodyValidation,
|
||||
loginValidation,
|
||||
recoveryValidation,
|
||||
recoveryTokenBodyValidation,
|
||||
newPasswordValidation,
|
||||
} from "@/api/validation/authValidation.js";
|
||||
|
||||
import {
|
||||
editUserBodyValidation,
|
||||
createUserBodyValidation,
|
||||
getUserByIdParamValidation,
|
||||
editUserByIdParamValidation,
|
||||
editUserByIdBodyValidation,
|
||||
editSuperadminUserByIdBodyValidation,
|
||||
editUserPasswordByIdBodyValidation,
|
||||
} from "@/api/validation/userValidation.js";
|
||||
import { IUserService } from "@/domain/users/user.service.js";
|
||||
|
||||
const SERVICE_NAME = "authController";
|
||||
|
||||
export interface IAuthController {
|
||||
registerUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
createUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
loginUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
editUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
checkSuperadminExists(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
requestRecovery(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
validateRecovery(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
resetPassword(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
deleteUser(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
deleteUserById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
getAllUsers(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
getUserById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
editUserById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
editUserPasswordById(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
}
|
||||
|
||||
class AuthController implements IAuthController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private userService: IUserService;
|
||||
|
||||
constructor(userService: IUserService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return AuthController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
registerUser = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const newUser = req.body.user;
|
||||
const newUserToken = req.body.token;
|
||||
if (newUser?.email) {
|
||||
const newUserEmail = requireUserEmail(newUser.email);
|
||||
newUser.email = newUserEmail.toLowerCase();
|
||||
}
|
||||
const validatedBody = registrationBodyValidation.parse(newUser);
|
||||
|
||||
const { user, token } = await this.userService.registerUser(validatedBody, newUserToken, req?.file ?? null);
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
msg: "User registered successfully",
|
||||
data: { user, token },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
createUser = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const userData = req.body;
|
||||
if (userData?.email) {
|
||||
userData.email = userData.email.toLowerCase();
|
||||
}
|
||||
const validatedBody = createUserBodyValidation.parse(userData);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const actorRoles = requireUserRoles(req.user?.role);
|
||||
const newUser = await this.userService.createUser(validatedBody, teamId, actorRoles, req?.file ?? null);
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
msg: "User created successfully",
|
||||
data: newUser,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
loginUser = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
if (req.body?.email) {
|
||||
req.body.email = req.body.email?.toLowerCase();
|
||||
}
|
||||
loginValidation.parse(req.body);
|
||||
const { user, token } = await this.userService.loginUser(req.body.email, req.body.password);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "User logged in successfully",
|
||||
data: {
|
||||
user,
|
||||
token,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
editUser = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedBody = editUserBodyValidation.parse(req.body);
|
||||
const userId = requireUserId(req.user?.id);
|
||||
const userEmail = requireUserEmail(req.user?.email);
|
||||
const updatedUser = await this.userService.editUser(validatedBody, req?.file ?? null, userId, userEmail);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
msg: "User updated successfully",
|
||||
data: updatedUser,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
checkSuperadminExists = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const superAdminExists = await this.userService.checkSuperadminExists();
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Superadmin existence checked successfully",
|
||||
data: superAdminExists,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
requestRecovery = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
recoveryValidation.parse(req.body);
|
||||
const email = req?.body?.email;
|
||||
const msgId = await this.userService.requestRecovery(email);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Password recovery email sent successfully",
|
||||
data: msgId,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
validateRecovery = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
recoveryTokenBodyValidation.parse(req.body);
|
||||
await this.userService.validateRecovery(req.body.recoveryToken);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Recovery token is valid",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
resetPassword = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
newPasswordValidation.parse(req.body);
|
||||
const { user, token } = await this.userService.resetPassword(req.body.password, req.body.recoveryToken);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Password has been reset successfully",
|
||||
data: { user, token },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
deleteUser = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const userId = requireUserId(req.user?.id);
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const roles = requireUserRoles(req.user?.role);
|
||||
|
||||
await this.userService.deleteUser({
|
||||
userId,
|
||||
teamId,
|
||||
roles,
|
||||
});
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "User deleted successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
deleteUserById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getUserByIdParamValidation.parse(req.params);
|
||||
const targetUserId = validatedParams.userId;
|
||||
const actorId = requireUserId(req.user?.id);
|
||||
const actorTeamId = requireTeamId(req.user?.teamId);
|
||||
const actorRoles = requireUserRoles(req.user?.role);
|
||||
await this.userService.deleteUserById({ actorId, actorTeamId, actorRoles, targetUserId });
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "User removed successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getAllUsers = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const allUsers = await this.userService.getAllUsers();
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Users retrieved successfully",
|
||||
data: allUsers,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getUserById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getUserByIdParamValidation.parse(req.params);
|
||||
const actorRoles = requireUserRoles(req.user?.role);
|
||||
const user = await this.userService.getUserById(actorRoles, validatedParams.userId);
|
||||
|
||||
return res.status(200).json({ success: true, msg: "ok", data: user });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
editUserById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const actorRoles = requireUserRoles(req.user?.role);
|
||||
const actorId = requireUserId(req.user?.id);
|
||||
|
||||
if (!actorRoles.includes("superadmin")) {
|
||||
throw new AppError({ message: "Unauthorized", status: 403 });
|
||||
}
|
||||
|
||||
const validatedParams = editUserByIdParamValidation.parse(req.params);
|
||||
// If this is superadmin self edit, allow "superadmin" role
|
||||
const validatedBody =
|
||||
validatedParams.userId === actorId ? editSuperadminUserByIdBodyValidation.parse(req.body) : editUserByIdBodyValidation.parse(req.body);
|
||||
|
||||
await this.userService.editUserById(validatedParams.userId, validatedBody);
|
||||
return res.status(200).json({ success: true, msg: "ok" });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const actorRoles = requireUserRoles(req.user?.role);
|
||||
if (!actorRoles.includes("superadmin")) {
|
||||
throw new AppError({ message: "Unauthorized", status: 403 });
|
||||
}
|
||||
|
||||
const validatedParams = editUserByIdParamValidation.parse(req.params);
|
||||
const validatedBody = editUserPasswordByIdBodyValidation.parse(req.body);
|
||||
await this.userService.setPasswordByUserId(validatedParams.userId, validatedBody.password);
|
||||
return res.status(200).json({ success: true, msg: "Password reset successfully" });
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default AuthController;
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
|
||||
import {
|
||||
getChecksParamValidation,
|
||||
getChecksQueryValidation,
|
||||
getTeamChecksQueryValidation,
|
||||
getChecksSummaryByTeamIdQueryValidation,
|
||||
deleteChecksParamValidation,
|
||||
} from "@/api/validation/checkValidation.js";
|
||||
import { ICheckService } from "@/domain/checks/check.service.js";
|
||||
import { requireTeamId } from "@/api/controllers/controllerUtils.js";
|
||||
|
||||
const SERVICE_NAME = "checkController";
|
||||
|
||||
export interface ICheckController {
|
||||
serviceName: string;
|
||||
getChecksByMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getChecksByTeam: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getChecksSummaryByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
deleteChecks: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
deleteChecksByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
}
|
||||
|
||||
class CheckController implements ICheckController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private checkService: ICheckService;
|
||||
constructor(checkService: ICheckService) {
|
||||
this.checkService = checkService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return CheckController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getChecksParamValidation.parse(req.params);
|
||||
const validatedQuery = getChecksQueryValidation.parse(req.query);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const result = await this.checkService.getChecksByMonitor({
|
||||
monitorId: validatedParams.monitorId,
|
||||
teamId,
|
||||
sortOrder: validatedQuery.sortOrder,
|
||||
dateRange: validatedQuery.dateRange,
|
||||
filter: validatedQuery.filter,
|
||||
page: validatedQuery.page,
|
||||
rowsPerPage: validatedQuery.rowsPerPage,
|
||||
status: validatedQuery.status,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Checks retrieved successfully",
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getChecksByTeam = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedQuery = getTeamChecksQueryValidation.parse(req.query);
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const checkData = await this.checkService.getChecksByTeam({
|
||||
teamId,
|
||||
sortOrder: validatedQuery.sortOrder,
|
||||
dateRange: validatedQuery.dateRange,
|
||||
page: validatedQuery.page,
|
||||
rowsPerPage: validatedQuery.rowsPerPage,
|
||||
filter: validatedQuery.filter,
|
||||
});
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Team checks retrieved successfully",
|
||||
data: checkData,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getChecksSummaryByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedQuery = getChecksSummaryByTeamIdQueryValidation.parse(req.query);
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const dateRange = validatedQuery.dateRange ?? "hour";
|
||||
|
||||
const summary = await this.checkService.getChecksSummaryByTeamId({ teamId, dateRange });
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Checks summary retrieved successfully",
|
||||
data: summary,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
deleteChecks = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = deleteChecksParamValidation.parse(req.params);
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const deletedCount = await this.checkService.deleteChecks({
|
||||
monitorId: validatedParams.monitorId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Checks deleted successfully",
|
||||
data: { deletedCount },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
deleteChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const deletedCount = await this.checkService.deleteChecksByTeamId({ teamId });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Checks deleted successfully",
|
||||
data: { deletedCount },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default CheckController;
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { Monitor, MonitorTypes, type MonitorType } from "@/domain/monitors/monitor.types.js";
|
||||
import { UserRole } from "@/domain/users/user.type.js";
|
||||
import sslChecker, { SSLDetails } from "ssl-checker";
|
||||
type SSLCheckerType = typeof sslChecker;
|
||||
|
||||
export const fetchMonitorCertificate = async (checker: SSLCheckerType, monitor: Monitor): Promise<SSLDetails> => {
|
||||
const monitorUrl = new URL(monitor.url);
|
||||
const hostname = monitorUrl.hostname;
|
||||
const cert = await checker(hostname);
|
||||
if (cert?.validTo === null || cert?.validTo === undefined) {
|
||||
throw new Error("Certificate not found");
|
||||
}
|
||||
return cert;
|
||||
};
|
||||
export const requireString = (value: unknown, fieldName: string): string => {
|
||||
if (typeof value === "string" && value.trim().length > 0) {
|
||||
return value;
|
||||
}
|
||||
throw new AppError({ message: `${fieldName} is required`, status: 400 });
|
||||
};
|
||||
|
||||
export const optionalString = (value: unknown, fieldName: string): string | undefined => {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
throw new AppError({ message: `${fieldName} must be a string`, status: 400 });
|
||||
};
|
||||
|
||||
export const extractString = (value: unknown): string | undefined => {
|
||||
const candidate = Array.isArray(value) ? value[0] : value;
|
||||
if (typeof candidate !== "string" || candidate.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return candidate;
|
||||
};
|
||||
|
||||
export const optionalNumber = (value: unknown, fieldName: string): number | undefined => {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value === "number" && Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string" && value.trim() !== "") {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isNaN(parsed)) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
throw new AppError({ message: `${fieldName} must be a number`, status: 400 });
|
||||
};
|
||||
|
||||
export const optionalBoolean = (value: unknown, fieldName: string): boolean | undefined => {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
if (value === "true") {
|
||||
return true;
|
||||
}
|
||||
if (value === "false") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
throw new AppError({ message: `${fieldName} must be a boolean`, status: 400 });
|
||||
};
|
||||
|
||||
export const parseMonitorTypeFilter = (value: unknown): MonitorType | MonitorType[] | undefined => {
|
||||
const parseSingle = (input: unknown): MonitorType => {
|
||||
if (typeof input !== "string") {
|
||||
throw new AppError({ message: "Monitor type must be a string", status: 400 });
|
||||
}
|
||||
if (!MonitorTypes.includes(input as MonitorType)) {
|
||||
throw new AppError({ message: `Invalid monitor type: ${input}`, status: 400 });
|
||||
}
|
||||
return input as MonitorType;
|
||||
};
|
||||
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((entry) => parseSingle(entry));
|
||||
}
|
||||
return parseSingle(value);
|
||||
};
|
||||
|
||||
export const parseSortOrder = (value: unknown): "asc" | "desc" | undefined => {
|
||||
if (value === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
if (value === "asc" || value === "desc") {
|
||||
return value;
|
||||
}
|
||||
throw new AppError({ message: "order must be either 'asc' or 'desc'", status: 400 });
|
||||
};
|
||||
|
||||
export const requireTeamId = (teamId?: string): string => {
|
||||
if (!teamId) {
|
||||
throw new AppError({ message: "Team ID is required", status: 400 });
|
||||
}
|
||||
return teamId;
|
||||
};
|
||||
|
||||
export const requireUserId = (userId?: string): string => {
|
||||
if (!userId) {
|
||||
throw new AppError({ message: "User ID is required", status: 400 });
|
||||
}
|
||||
return userId;
|
||||
};
|
||||
export const requireUserEmail = (userEmail?: string): string => {
|
||||
if (!userEmail) {
|
||||
throw new AppError({ message: "User email is required", status: 400 });
|
||||
}
|
||||
return userEmail;
|
||||
};
|
||||
|
||||
export const requireFirstName = (firstName?: string): string => {
|
||||
if (!firstName) {
|
||||
throw new AppError({ message: "First name is required", status: 400 });
|
||||
}
|
||||
return firstName;
|
||||
};
|
||||
|
||||
export const requireUserRoles = (userRoles?: UserRole[]): UserRole[] => {
|
||||
if (!userRoles || userRoles.length === 0) {
|
||||
throw new AppError({ message: "User roles are required", status: 400 });
|
||||
}
|
||||
return userRoles;
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { IDiagnosticService } from "@/domain/diagnostics/diagnostic.service.js";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
|
||||
const SERVICE_NAME = "diagnosticController";
|
||||
|
||||
export interface IDiagnosticController {
|
||||
getSystemStats: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
}
|
||||
|
||||
class DiagnosticController implements IDiagnosticController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private diagnosticService: IDiagnosticService;
|
||||
|
||||
constructor(diagnosticService: IDiagnosticService) {
|
||||
this.diagnosticService = diagnosticService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return DiagnosticController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getSystemStats = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const diagnostics = await this.diagnosticService.getSystemStats();
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "OK",
|
||||
data: diagnostics,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default DiagnosticController;
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { getChecksParamValidation, getChecksQueryValidation } from "@/api/validation/checkValidation.js";
|
||||
import type { IGeoChecksService } from "@/domain/geo-checks/geo-check.service.js";
|
||||
import { requireTeamId } from "./controllerUtils.js";
|
||||
|
||||
const SERVICE_NAME = "geoCheckController";
|
||||
|
||||
export interface IGeoCheckController {
|
||||
getGeoChecksByMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
}
|
||||
class GeoCheckController implements IGeoCheckController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private geoChecksService: IGeoChecksService;
|
||||
constructor(geoChecksService: IGeoChecksService) {
|
||||
this.geoChecksService = geoChecksService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return GeoCheckController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getGeoChecksByMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getChecksParamValidation.parse(req.params);
|
||||
const validatedQuery = getChecksQueryValidation.parse(req.query);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const result = await this.geoChecksService.getGeoChecksByMonitor({
|
||||
monitorId: validatedParams.monitorId,
|
||||
teamId: teamId,
|
||||
sortOrder: validatedQuery.sortOrder,
|
||||
dateRange: validatedQuery.dateRange,
|
||||
page: validatedQuery.page,
|
||||
rowsPerPage: validatedQuery.rowsPerPage,
|
||||
continent: validatedQuery.continent,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Geo checks retrieved successfully",
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default GeoCheckController;
|
||||
@@ -0,0 +1,107 @@
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { requireTeamId, requireUserId, requireUserEmail, extractString } from "./controllerUtils.js";
|
||||
import { IIncidentService } from "@/domain/incidents/incident.service.js";
|
||||
import { getIncidentsByTeamQueryValidation, getIncidentSummaryQueryValidation } from "@/api/validation/incidentValidation.js";
|
||||
|
||||
const SERVICE_NAME = "IncidentController";
|
||||
|
||||
export interface IIncidentController {
|
||||
getIncidentsByTeam: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getIncidentSummary: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getIncidentById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
resolveIncidentManually: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
}
|
||||
class IncidentController implements IIncidentController {
|
||||
private incidentService: IIncidentService;
|
||||
constructor(incidentService: IIncidentService) {
|
||||
this.incidentService = incidentService;
|
||||
}
|
||||
|
||||
getIncidentsByTeam = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedQuery = getIncidentsByTeamQueryValidation.parse(req.query);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const result = await this.incidentService.getIncidentsByTeam(
|
||||
teamId,
|
||||
validatedQuery.sortOrder,
|
||||
validatedQuery.dateRange,
|
||||
validatedQuery.page,
|
||||
validatedQuery.rowsPerPage,
|
||||
validatedQuery.status,
|
||||
validatedQuery.monitorId,
|
||||
validatedQuery.resolutionType
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Incidents retrieved successfully",
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getIncidentSummary = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const validatedQuery = getIncidentSummaryQueryValidation.parse(req.query);
|
||||
|
||||
const summary = await this.incidentService.getIncidentSummary(teamId, validatedQuery.limit);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Incident summary retrieved successfully",
|
||||
data: summary,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getIncidentById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const incidentId = req.params.incidentId as string;
|
||||
if (!incidentId) {
|
||||
throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
|
||||
}
|
||||
|
||||
const incident = await this.incidentService.getIncidentById(incidentId, teamId);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Incident retrieved successfully",
|
||||
data: incident,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
resolveIncidentManually = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const userId = requireUserId(req.user?.id);
|
||||
const userEmail = requireUserEmail(req.user?.email);
|
||||
const incidentId = extractString(req.params?.incidentId);
|
||||
if (!incidentId) {
|
||||
throw new AppError({ message: "Incident ID is required", service: SERVICE_NAME, status: 400 });
|
||||
}
|
||||
|
||||
const comment = extractString(req.body?.comment);
|
||||
const resolvedIncident = await this.incidentService.resolveIncident(incidentId, userId, teamId, comment, userEmail);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Incident resolved successfully",
|
||||
data: resolvedIncident,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default IncidentController;
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { inviteBodyValidation, inviteVerificationBodyValidation } from "@/api/validation/authValidation.js";
|
||||
import { requireFirstName, requireTeamId, requireUserRoles } from "@/api/controllers/controllerUtils.js";
|
||||
import { IInviteService } from "@/domain/invites/invite.service.js";
|
||||
const SERVICE_NAME = "inviteController";
|
||||
|
||||
export interface IInviteController {
|
||||
getInviteToken: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
sendInviteEmail: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
verifyInviteToken: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
}
|
||||
|
||||
class InviteController implements IInviteController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private inviteService: IInviteService;
|
||||
constructor(inviteService: IInviteService) {
|
||||
this.inviteService = inviteService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return InviteController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getInviteToken = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const userRoles = requireUserRoles(req.user?.role);
|
||||
const invite = req.body;
|
||||
invite.teamId = teamId;
|
||||
inviteBodyValidation.parse(invite);
|
||||
const inviteToken = await this.inviteService.getInviteToken({ invite, teamId, userRoles });
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Invite token generated successfully",
|
||||
data: inviteToken,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
sendInviteEmail = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const userRoles = requireUserRoles(req.user?.role);
|
||||
const firstName = requireFirstName(req.user?.firstName);
|
||||
|
||||
const inviteRequest = req.body;
|
||||
inviteRequest.teamId = teamId;
|
||||
inviteBodyValidation.parse(inviteRequest);
|
||||
|
||||
const inviteToken = await this.inviteService.sendInviteEmail({
|
||||
invite: inviteRequest,
|
||||
firstName,
|
||||
userRoles,
|
||||
});
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Invite issued successfully",
|
||||
data: inviteToken,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
verifyInviteToken = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
inviteVerificationBodyValidation.parse(req.body);
|
||||
const invite = await this.inviteService.verifyInviteToken({ inviteToken: req?.body?.token });
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Invite verified successfully",
|
||||
data: invite,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default InviteController;
|
||||
@@ -0,0 +1,33 @@
|
||||
import { ILogger } from "@/utils/logger.js";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
|
||||
const SERVICE_NAME = "LogController";
|
||||
|
||||
export interface ILogController {
|
||||
getLogs: (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
||||
}
|
||||
|
||||
class LogController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private logger: ILogger;
|
||||
constructor(logger: ILogger) {
|
||||
this.logger = logger;
|
||||
}
|
||||
get serviceName() {
|
||||
return LogController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getLogs = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const logs = this.logger.getLogs();
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
msg: "Logs fetched successfully",
|
||||
data: logs,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
export default LogController;
|
||||
@@ -0,0 +1,155 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import {
|
||||
createMaintenanceWindowBodyValidation,
|
||||
editMaintenanceWindowByIdParamValidation,
|
||||
editMaintenanceByIdWindowBodyValidation,
|
||||
getMaintenanceWindowByIdParamValidation,
|
||||
getMaintenanceWindowsByMonitorIdParamValidation,
|
||||
getMaintenanceWindowsByTeamIdQueryValidation,
|
||||
deleteMaintenanceWindowByIdParamValidation,
|
||||
} from "@/api/validation/maintenanceWindowValidation.js";
|
||||
import { requireTeamId } from "@/api/controllers/controllerUtils.js";
|
||||
import { IMaintenanceWindowService } from "@/domain/maintenance-windows/maintenance-window.service.js";
|
||||
|
||||
export interface IMaintenanceWindowController {
|
||||
createMaintenanceWindows: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getMaintenanceWindowById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getMaintenanceWindowsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getMaintenanceWindowsByMonitorId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
deleteMaintenanceWindow: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
editMaintenanceWindow: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
}
|
||||
class MaintenanceWindowController implements IMaintenanceWindowController {
|
||||
private maintenanceWindowService: IMaintenanceWindowService;
|
||||
constructor(maintenanceWindowService: IMaintenanceWindowService) {
|
||||
this.maintenanceWindowService = maintenanceWindowService;
|
||||
}
|
||||
|
||||
createMaintenanceWindows = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedBody = createMaintenanceWindowBodyValidation.parse(req.body);
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const monitorIDs = validatedBody.monitors;
|
||||
const name = validatedBody.name;
|
||||
const active = validatedBody.active ?? true;
|
||||
const duration = validatedBody.duration;
|
||||
const durationUnit = validatedBody.durationUnit;
|
||||
const repeat = validatedBody.repeat;
|
||||
const start = validatedBody.start;
|
||||
const end = validatedBody.end;
|
||||
|
||||
await this.maintenanceWindowService.createMaintenanceWindow({ teamId, monitorIDs, name, active, duration, durationUnit, repeat, start, end });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Maintenance window created successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
getMaintenanceWindowById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getMaintenanceWindowByIdParamValidation.parse(req.params);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const maintenanceWindow = await this.maintenanceWindowService.getMaintenanceWindowById({ id: validatedParams.id, teamId });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Maintenance window fetched successfully",
|
||||
data: maintenanceWindow,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getMaintenanceWindowsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedQuery = getMaintenanceWindowsByTeamIdQueryValidation.parse(req.query);
|
||||
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByTeamId({
|
||||
teamId,
|
||||
active: validatedQuery.active,
|
||||
page: validatedQuery.page,
|
||||
rowsPerPage: validatedQuery.rowsPerPage,
|
||||
field: validatedQuery.field,
|
||||
order: validatedQuery.order,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Maintenance windows fetched successfully",
|
||||
data: maintenanceWindows,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getMaintenanceWindowsByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getMaintenanceWindowsByMonitorIdParamValidation.parse(req.params);
|
||||
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
const maintenanceWindows = await this.maintenanceWindowService.getMaintenanceWindowsByMonitorId({
|
||||
monitorId: validatedParams.monitorId,
|
||||
teamId,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Maintenance windows fetched successfully",
|
||||
data: maintenanceWindows,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
deleteMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = deleteMaintenanceWindowByIdParamValidation.parse(req.params);
|
||||
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
|
||||
await this.maintenanceWindowService.deleteMaintenanceWindow({ id: validatedParams.id, teamId });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Maintenance window deleted successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
editMaintenanceWindow = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = editMaintenanceWindowByIdParamValidation.parse(req.params);
|
||||
const validatedBody = editMaintenanceByIdWindowBodyValidation.parse(req.body);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const editedMaintenanceWindow = await this.maintenanceWindowService.editMaintenanceWindow({
|
||||
id: validatedParams.id,
|
||||
body: validatedBody,
|
||||
teamId,
|
||||
});
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Maintenance window edited successfully",
|
||||
data: editedMaintenanceWindow,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MaintenanceWindowController;
|
||||
@@ -0,0 +1,490 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { updateNotificationsValidation } from "@/api/validation/notificationValidation.js";
|
||||
import {
|
||||
getMonitorByIdParamValidation,
|
||||
getMonitorByIdQueryValidation,
|
||||
getMonitorsByTeamIdQueryValidation,
|
||||
getMonitorsWithChecksQueryValidation,
|
||||
createMonitorBodyValidation,
|
||||
editMonitorBodyValidation,
|
||||
pauseMonitorParamValidation,
|
||||
getCertificateParamValidation,
|
||||
getHardwareDetailsByIdParamValidation,
|
||||
getHardwareDetailsByIdQueryValidation,
|
||||
getUptimeDetailsByIdParamValidation,
|
||||
getUptimeDetailsByIdQueryValidation,
|
||||
importMonitorsBodyValidation,
|
||||
bulkPauseMonitorBodyValidation,
|
||||
} from "@/api/validation/monitorValidation.js";
|
||||
import sslChecker from "ssl-checker";
|
||||
import { fetchMonitorCertificate, requireTeamId, requireUserId } from "@/api/controllers/controllerUtils.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { IMonitorService } from "@/domain/monitors/monitor.service.js";
|
||||
import { INotificationsService } from "@/domain/notifications/notification.service.js";
|
||||
|
||||
const SERVICE_NAME = "monitorController";
|
||||
|
||||
export interface IMonitorController {
|
||||
getMonitorCertificate: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getUptimeDetailsById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getHardwareDetailsById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getPageSpeedDetailsById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getGeoChecksByMonitorId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getMonitorById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
createMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
importMonitorsFromJSON: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
deleteMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
deleteAllMonitors: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
editMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
pauseMonitor: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
bulkPauseMonitors: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
addDemoMonitors: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getMonitorsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getMonitorsWithChecksByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
exportMonitorsToJSON: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getAllGames: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getGroupsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
updateNotifications: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
}
|
||||
class MonitorController implements IMonitorController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
|
||||
private monitorService: IMonitorService;
|
||||
private notificationsService: INotificationsService;
|
||||
|
||||
constructor(monitorService: IMonitorService, notificationsService: INotificationsService) {
|
||||
this.monitorService = monitorService;
|
||||
this.notificationsService = notificationsService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return MonitorController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getMonitorCertificate = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getCertificateParamValidation.parse(req.params);
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId: validatedParams.monitorId });
|
||||
const certificate = await fetchMonitorCertificate(sslChecker, monitor);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "SSL certificate retrieved successfully",
|
||||
data: {
|
||||
certificateDate: new Date(certificate.validTo),
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getUptimeDetailsById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getUptimeDetailsByIdParamValidation.parse(req.params);
|
||||
const validatedQuery = getUptimeDetailsByIdQueryValidation.parse(req.query);
|
||||
|
||||
const monitorId = validatedParams.monitorId;
|
||||
const dateRange = validatedQuery.dateRange;
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const data = await this.monitorService.getUptimeDetailsById({
|
||||
teamId,
|
||||
monitorId,
|
||||
dateRange,
|
||||
});
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Uptime details retrieved successfully",
|
||||
data: data,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getHardwareDetailsById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
|
||||
const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
|
||||
|
||||
const monitorId = validatedParams.monitorId;
|
||||
const dateRange = validatedQuery.dateRange || "recent";
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const data = await this.monitorService.getHardwareDetailsById({
|
||||
teamId,
|
||||
monitorId,
|
||||
dateRange,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Hardware details retrieved successfully",
|
||||
data: data,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
getPageSpeedDetailsById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getHardwareDetailsByIdParamValidation.parse(req.params);
|
||||
const validatedQuery = getHardwareDetailsByIdQueryValidation.parse(req.query);
|
||||
|
||||
const monitorId = validatedParams.monitorId;
|
||||
const dateRange = validatedQuery.dateRange || "recent";
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const data = await this.monitorService.getPageSpeedDetailsById({
|
||||
teamId,
|
||||
monitorId,
|
||||
dateRange,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Page speed details retrieved successfully",
|
||||
data,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getGeoChecksByMonitorId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
|
||||
const validatedQuery = getMonitorByIdQueryValidation.parse(req.query);
|
||||
|
||||
const monitorId = validatedParams.monitorId;
|
||||
const dateRange = validatedQuery.dateRange || "recent";
|
||||
const continentParam = validatedQuery.continent;
|
||||
const continents = continentParam ? (Array.isArray(continentParam) ? continentParam : [continentParam]) : undefined;
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const data = await this.monitorService.getGeoChecksByMonitorId({
|
||||
teamId,
|
||||
monitorId,
|
||||
dateRange,
|
||||
continents,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Geo checks retrieved successfully",
|
||||
data,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getMonitorById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const monitorId = validatedParams.monitorId;
|
||||
|
||||
const monitor = await this.monitorService.getMonitorById({ teamId, monitorId });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Monitor retrieved successfully",
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
createMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedBody = createMonitorBodyValidation.parse(req.body);
|
||||
|
||||
const userId = requireUserId(req.user?.id);
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const monitor = await this.monitorService.createMonitor(teamId, userId, validatedBody);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Monitor created successfully",
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
importMonitorsFromJSON = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const userId = requireUserId(req.user?.id);
|
||||
const validatedBody = importMonitorsBodyValidation.parse(req.body);
|
||||
const monitors = validatedBody.monitors;
|
||||
|
||||
const result = await this.monitorService.importMonitorsFromJSON({ teamId, userId, monitors });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: `Successfully imported ${result.imported} monitor(s)`,
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
deleteMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const monitorId = validatedParams.monitorId;
|
||||
|
||||
const deletedMonitor = await this.monitorService.deleteMonitor({ teamId, monitorId });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Monitor deleted successfully",
|
||||
data: deletedMonitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
deleteAllMonitors = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const deletedCount = await this.monitorService.deleteAllMonitors({ teamId });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: `Deleted ${deletedCount} monitors`,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
editMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = getMonitorByIdParamValidation.parse(req.params);
|
||||
const validatedBody = editMonitorBodyValidation.parse(req.body);
|
||||
const monitorId = validatedParams.monitorId;
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const editedMonitor = await this.monitorService.editMonitor({ teamId, monitorId, body: validatedBody });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Monitor edited successfully",
|
||||
data: editedMonitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
pauseMonitor = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedParams = pauseMonitorParamValidation.parse(req.params);
|
||||
|
||||
const monitorId = validatedParams.monitorId;
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const monitor = await this.monitorService.pauseMonitor({ teamId, monitorId });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: monitor.isActive ? "Monitor resumed successfully" : "Monitor paused successfully",
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
bulkPauseMonitors = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedBody = bulkPauseMonitorBodyValidation.parse(req.body);
|
||||
|
||||
const { monitorIds, pause } = validatedBody;
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const { monitors, failedCount } = await this.monitorService.bulkPauseMonitors({ teamId, monitorIds, pause });
|
||||
|
||||
const action = pause ? "paused" : "resumed";
|
||||
const monitorStr = monitors.length === 1 ? "monitor" : "monitors";
|
||||
|
||||
let msg = `${monitors.length} ${monitorStr} ${action} successfully`;
|
||||
if (failedCount > 0) {
|
||||
msg = `${monitors.length} ${monitorStr} ${action} in database, but ${failedCount} failed to sync with the job queue. Please check logs.`;
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg,
|
||||
data: { monitors, failedCount },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const id = requireUserId(req.user?.id);
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const demoMonitors = await this.monitorService.addDemoMonitors({ userId: id, teamId });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Demo monitors added successfully",
|
||||
data: demoMonitors?.length ?? 0,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getMonitorsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedQuery = getMonitorsByTeamIdQueryValidation.parse(req.query);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const type = validatedQuery.type;
|
||||
const tags = validatedQuery.tags;
|
||||
const filter = validatedQuery.filter;
|
||||
|
||||
const monitors = await this.monitorService.getMonitorsByTeamId({ teamId, type, tags, filter });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Monitors retrieved successfully",
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getMonitorsWithChecksByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedQuery = getMonitorsWithChecksQueryValidation.parse(req.query);
|
||||
|
||||
const limit = validatedQuery.limit;
|
||||
const page = validatedQuery.page;
|
||||
const rowsPerPage = validatedQuery.rowsPerPage;
|
||||
const filter = validatedQuery.filter;
|
||||
const field = validatedQuery.field;
|
||||
const order = validatedQuery.order;
|
||||
const type = validatedQuery.type;
|
||||
const tags = validatedQuery.tags;
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const monitors = await this.monitorService.getMonitorsWithChecksByTeamId({
|
||||
teamId,
|
||||
limit,
|
||||
type,
|
||||
tags,
|
||||
page,
|
||||
rowsPerPage,
|
||||
filter,
|
||||
field,
|
||||
order,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
msg: "Monitors retrieved successfully",
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
exportMonitorsToJSON = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const json = await this.monitorService.exportMonitorsToJSON({ teamId });
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Monitors exported successfully",
|
||||
data: json,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getAllGames = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const games = this.monitorService.getAllGames();
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Supported games retrieved successfully",
|
||||
data: games,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getGroupsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const groups = await this.monitorService.getGroupsByTeamId({ teamId });
|
||||
|
||||
return res.status(200).json({
|
||||
msg: "Groups retrieved successfully",
|
||||
data: groups,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
updateNotifications = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
updateNotificationsValidation.parse(req.body);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const { monitorIds, notificationIds, action } = req.body;
|
||||
|
||||
// Verify all requested notification IDs actually belong to this team
|
||||
const teamNotifications = await this.notificationsService.findNotificationsByTeamId(teamId);
|
||||
const validNotificationIds = teamNotifications.map((n) => n.id);
|
||||
|
||||
const invalidIds = notificationIds.filter((id: string) => !validNotificationIds.includes(id));
|
||||
if (invalidIds.length > 0) {
|
||||
throw new AppError({
|
||||
message: `The following notification IDs are invalid or do not belong to your team: ${invalidIds.join(", ")}`,
|
||||
status: 403,
|
||||
});
|
||||
}
|
||||
|
||||
const modifiedCount = await this.monitorService.updateNotifications({
|
||||
teamId,
|
||||
monitorIds,
|
||||
notificationIds,
|
||||
action,
|
||||
});
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: `Notifications updated successfully on ${modifiedCount} monitor(s)`,
|
||||
data: { modifiedCount },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default MonitorController;
|
||||
@@ -0,0 +1,163 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
|
||||
import {
|
||||
createNotificationBodyValidation,
|
||||
deleteNotificationParamValidation,
|
||||
getNotificationByIdParamValidation,
|
||||
testNotificationBodyValidation,
|
||||
editNotificationParamValidation,
|
||||
testAllNotificationsBodyValidation,
|
||||
} from "@/api/validation/notificationValidation.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { INotificationsService } from "@/domain/notifications/notification.service.js";
|
||||
import { requireTeamId, requireUserId } from "./controllerUtils.js";
|
||||
import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js";
|
||||
|
||||
const SERVICE_NAME = "NotificationController";
|
||||
|
||||
export interface INotificationController {
|
||||
testNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
createNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getNotificationsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
deleteNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getNotificationById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
editNotification: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
testAllNotifications: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
}
|
||||
class NotificationController implements INotificationController {
|
||||
private notificationsService: INotificationsService;
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
constructor(notificationsService: INotificationsService, monitorsRepository: IMonitorsRepository) {
|
||||
this.notificationsService = notificationsService;
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
}
|
||||
|
||||
testNotification = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const notification = testNotificationBodyValidation.parse(req.body);
|
||||
const success = await this.notificationsService.sendTestNotification(notification);
|
||||
|
||||
return res.status(200).json({
|
||||
success,
|
||||
msg: success ? "Notification sent successfully" : "Notification could not be sent — check the destination details.",
|
||||
details: { service: SERVICE_NAME },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
createNotification = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedBody = createNotificationBodyValidation.parse(req.body);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const userId = requireUserId(req.user?.id);
|
||||
|
||||
const notification = await this.notificationsService.createNotification(validatedBody, userId, teamId);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Notification created successfully",
|
||||
data: notification,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getNotificationsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const notifications = await this.notificationsService.findNotificationsByTeamId(teamId);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Notifications fetched successfully",
|
||||
data: notifications,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
deleteNotification = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const validatedParams = deleteNotificationParamValidation.parse(req.params);
|
||||
|
||||
await this.notificationsService.deleteById(validatedParams.id, teamId);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Notification deleted successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getNotificationById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const validatedParams = getNotificationByIdParamValidation.parse(req.params);
|
||||
|
||||
const notification = await this.notificationsService.findById(validatedParams.id, teamId);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Notification fetched successfully",
|
||||
data: notification,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
editNotification = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedBody = createNotificationBodyValidation.parse(req.body);
|
||||
const validatedParams = editNotificationParamValidation.parse(req.params);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const notificationId = validatedParams.id;
|
||||
|
||||
const editedNotification = await this.notificationsService.updateById(notificationId, teamId, validatedBody);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Notification updated successfully",
|
||||
data: editedNotification,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
testAllNotifications = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedBody = testAllNotificationsBodyValidation.parse(req.body);
|
||||
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const monitor = await this.monitorsRepository.findById(validatedBody.monitorId, teamId);
|
||||
const notifications = monitor.notifications || [];
|
||||
|
||||
if (notifications.length === 0) {
|
||||
throw new AppError({ message: "No notifications", status: 400 });
|
||||
}
|
||||
|
||||
const result = await this.notificationsService.testAllNotifications(notifications);
|
||||
|
||||
if (!result) {
|
||||
throw new AppError({ message: "Failed to send all notifications", status: 500 });
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "All notifications sent successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default NotificationController;
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
import { IJobQueue } from "@/service/job-queues/job-queue.interface.js";
|
||||
import { getQueueJobsQueryValidation } from "@/api/validation/queueValidation.js";
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
|
||||
const SERVICE_NAME = "JobQueueController";
|
||||
|
||||
export interface IJobQueueController {
|
||||
getMetrics(req: Request, res: Response, next: NextFunction): Promise<void>;
|
||||
getJobs(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
getAllMetrics(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
flushQueue(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
}
|
||||
|
||||
class JobQueueController implements IJobQueueController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private jobQueue: IJobQueue;
|
||||
constructor(jobQueue: IJobQueue) {
|
||||
this.jobQueue = jobQueue;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return JobQueueController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
getMetrics = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const metrics = await this.jobQueue.getMetrics();
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
msg: "Queue metrics fetched successfully",
|
||||
data: metrics,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getJobs = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const pagination = getQueueJobsQueryValidation.parse(req.query);
|
||||
const { jobs, count } = await this.jobQueue.getJobs(pagination);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Queue jobs fetched successfully",
|
||||
data: { jobs, count },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getAllMetrics = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const pagination = getQueueJobsQueryValidation.parse(req.query);
|
||||
const { jobs, count } = await this.jobQueue.getJobs(pagination);
|
||||
const metrics = await this.jobQueue.getMetrics();
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Queue metrics fetched successfully",
|
||||
data: { jobs, count, metrics },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
flushQueue = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const result = await this.jobQueue.flushQueues();
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Queue flushed successfully",
|
||||
data: result,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
export default JobQueueController;
|
||||
@@ -0,0 +1,143 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { updateAppSettingsBodyValidation } from "@/api/validation/settingsValidation.js";
|
||||
import { sendTestEmailBodyValidation } from "@/api/validation/notificationValidation.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { ISettingsService } from "@/domain/app-settings/app-settings.service.js";
|
||||
import { IEmailService } from "@/service/emailService.js";
|
||||
import { Settings } from "@/domain/app-settings/app-settings.type.js";
|
||||
|
||||
const SERVICE_NAME = "SettingsController";
|
||||
|
||||
export interface ISettingsController {
|
||||
serviceName: string;
|
||||
getAppSettings(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
updateAppSettings(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
sendTestEmail(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
}
|
||||
|
||||
class SettingsController implements ISettingsController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private settingsService: ISettingsService;
|
||||
private emailService: IEmailService;
|
||||
constructor(settingsService: ISettingsService, emailService: IEmailService) {
|
||||
this.settingsService = settingsService;
|
||||
this.emailService = emailService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return SettingsController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
buildAppSettings = (dbSettings: Settings) => {
|
||||
const sanitizedSettings: Record<string, unknown> = { ...dbSettings };
|
||||
delete sanitizedSettings.version;
|
||||
delete sanitizedSettings.jwtSecret;
|
||||
const returnSettings: Record<string, unknown | null> = {
|
||||
pagespeedKeySet: false,
|
||||
emailPasswordSet: false,
|
||||
settings: null,
|
||||
};
|
||||
|
||||
if (typeof sanitizedSettings.pagespeedApiKey !== "undefined") {
|
||||
returnSettings.pagespeedKeySet = true;
|
||||
delete sanitizedSettings.pagespeedApiKey;
|
||||
}
|
||||
if (typeof sanitizedSettings.systemEmailPassword !== "undefined") {
|
||||
returnSettings.emailPasswordSet = true;
|
||||
delete sanitizedSettings.systemEmailPassword;
|
||||
}
|
||||
returnSettings.settings = sanitizedSettings;
|
||||
return returnSettings;
|
||||
};
|
||||
|
||||
getAppSettings = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const dbSettings = await this.settingsService.getDBSettings();
|
||||
|
||||
const returnSettings = this.buildAppSettings(dbSettings);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "App settings fetched successfully",
|
||||
data: returnSettings,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
updateAppSettings = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedBody = updateAppSettingsBodyValidation.parse(req.body);
|
||||
|
||||
const updatedSettings = await this.settingsService.updateDbSettings(validatedBody);
|
||||
const returnSettings = this.buildAppSettings(updatedSettings);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "App settings updated successfully",
|
||||
data: returnSettings,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
sendTestEmail = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
sendTestEmailBodyValidation.parse(req.body);
|
||||
|
||||
const {
|
||||
to,
|
||||
systemEmailHost,
|
||||
systemEmailPort,
|
||||
systemEmailAddress,
|
||||
systemEmailDisplayName,
|
||||
systemEmailPassword,
|
||||
systemEmailUser,
|
||||
systemEmailConnectionHost,
|
||||
systemEmailSecure,
|
||||
systemEmailPool,
|
||||
systemEmailIgnoreTLS,
|
||||
systemEmailRequireTLS,
|
||||
systemEmailRejectUnauthorized,
|
||||
systemEmailTLSServername,
|
||||
} = req.body;
|
||||
|
||||
const subject = "This is a test email from Checkmate";
|
||||
const context = { testName: "Monitoring System" };
|
||||
|
||||
const html = await this.emailService.buildEmail("testEmailTemplate", context);
|
||||
if (!html) {
|
||||
throw new AppError({ message: "Failed to build email template.", status: 500 });
|
||||
}
|
||||
const messageId = await this.emailService.sendEmail(to, subject, html, {
|
||||
systemEmailHost,
|
||||
systemEmailPort,
|
||||
systemEmailUser,
|
||||
systemEmailAddress,
|
||||
systemEmailDisplayName,
|
||||
systemEmailPassword,
|
||||
systemEmailConnectionHost,
|
||||
systemEmailSecure,
|
||||
systemEmailPool,
|
||||
systemEmailIgnoreTLS,
|
||||
systemEmailRequireTLS,
|
||||
systemEmailRejectUnauthorized,
|
||||
systemEmailTLSServername,
|
||||
});
|
||||
|
||||
if (!messageId) {
|
||||
throw new AppError({ message: "Failed to send test email.", status: 500 });
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Test email sent successfully",
|
||||
data: { messageId },
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default SettingsController;
|
||||
@@ -0,0 +1,173 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
|
||||
import {
|
||||
createStatusPageBodyValidation,
|
||||
getStatusPageParamValidation,
|
||||
getStatusPageQueryValidation,
|
||||
imageValidation,
|
||||
} from "@/api/validation/statusPageValidation.js";
|
||||
import { AppError } from "@/utils/AppError.js";
|
||||
import { requireTeamId, requireUserId } from "@/api/controllers/controllerUtils.js";
|
||||
import { IStatusPageService } from "@/domain/status-pages/status-page.service.js";
|
||||
import { IMonitorsRepository } from "@/domain/monitors/monitor.repository.interface.js";
|
||||
import { ISettingsService } from "@/domain/app-settings/app-settings.service.js";
|
||||
import { NormalizeData } from "@/utils/dataUtils.js";
|
||||
|
||||
const SERVICE_NAME = "statusPageController";
|
||||
|
||||
export interface IStatusPageController {
|
||||
readonly serviceName: string;
|
||||
createStatusPage(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
updateStatusPage(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
getStatusPageByUrl(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
getStatusPagesByTeamId(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
deleteStatusPage(req: Request, res: Response, next: NextFunction): Promise<Response | void>;
|
||||
}
|
||||
|
||||
class StatusPageController implements IStatusPageController {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
private statusPageService: IStatusPageService;
|
||||
private monitorsRepository: IMonitorsRepository;
|
||||
private settingsService: ISettingsService;
|
||||
constructor(statusPageService: IStatusPageService, monitorsRepository: IMonitorsRepository, settingsService: ISettingsService) {
|
||||
this.statusPageService = statusPageService;
|
||||
this.monitorsRepository = monitorsRepository;
|
||||
this.settingsService = settingsService;
|
||||
}
|
||||
|
||||
get serviceName() {
|
||||
return StatusPageController.SERVICE_NAME;
|
||||
}
|
||||
|
||||
createStatusPage = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
createStatusPageBodyValidation.parse(req.body);
|
||||
if (req.file) {
|
||||
imageValidation.parse(req.file);
|
||||
}
|
||||
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
const userId = requireUserId(req?.user?.id);
|
||||
const statusPage = await this.statusPageService.createStatusPage(userId, teamId, req.file, req.body);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Status page created successfully",
|
||||
data: statusPage,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
updateStatusPage = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
createStatusPageBodyValidation.parse(req.body);
|
||||
if (req.file) {
|
||||
imageValidation.parse(req.file);
|
||||
}
|
||||
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
const statusPageId = req.params.id as string;
|
||||
if (!statusPageId) {
|
||||
throw new AppError({ message: "Status page ID is required", status: 400 });
|
||||
}
|
||||
const statusPage = await this.statusPageService.updateStatusPage(statusPageId, teamId, req.file, req.body);
|
||||
if (statusPage === null) {
|
||||
throw new AppError({ message: "Status page not found", status: 404 });
|
||||
}
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
msg: "Status page updated successfully",
|
||||
data: statusPage,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getStatusPageByUrl = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
getStatusPageParamValidation.parse(req.params);
|
||||
getStatusPageQueryValidation.parse(req.query);
|
||||
|
||||
if (!req.params.url) {
|
||||
throw new AppError({ message: "Status page URL is required", status: 400 });
|
||||
}
|
||||
|
||||
const statusPage = await this.statusPageService.getStatusPageByUrl(req.params.url as string);
|
||||
|
||||
if (!statusPage.isPublished) {
|
||||
const teamId = requireTeamId(req?.user?.teamId);
|
||||
if (statusPage.teamId !== teamId) {
|
||||
throw new AppError({ message: "Forbidden", status: 403 });
|
||||
}
|
||||
}
|
||||
|
||||
const settings = await this.settingsService.getDBSettings();
|
||||
const showURL = settings.showURL;
|
||||
|
||||
const monitors = await this.monitorsRepository.findByIds(statusPage.monitors);
|
||||
// Sort monitors according to the order in statusPage.monitors
|
||||
const monitorOrder = new Map(statusPage.monitors.map((id, index) => [id, index]));
|
||||
const sortedMonitors = [...monitors].sort((a, b) => {
|
||||
const orderA = monitorOrder.get(a.id) ?? Number.MAX_SAFE_INTEGER;
|
||||
const orderB = monitorOrder.get(b.id) ?? Number.MAX_SAFE_INTEGER;
|
||||
return orderA - orderB;
|
||||
});
|
||||
|
||||
const normalizedMonitors = sortedMonitors.map((monitor) => {
|
||||
const normalizedChecks = NormalizeData(monitor.recentChecks, 10, 100);
|
||||
if (!showURL) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const { url, port, secret, notifications, ...rest } = monitor;
|
||||
return { ...rest, checks: normalizedChecks };
|
||||
}
|
||||
return { ...monitor, checks: normalizedChecks };
|
||||
});
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Status page retrieved successfully",
|
||||
data: {
|
||||
statusPage,
|
||||
monitors: normalizedMonitors,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
getStatusPagesByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const statusPages = await this.statusPageService.getStatusPagesByTeamId(teamId);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Status pages retrieved successfully",
|
||||
data: statusPages,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
deleteStatusPage = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const statusPageId = req.params.id as string;
|
||||
if (!statusPageId) {
|
||||
throw new AppError({ message: "Status page ID is required", status: 400 });
|
||||
}
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
await this.statusPageService.deleteStatusPage(statusPageId, teamId);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Status page deleted successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default StatusPageController;
|
||||
@@ -0,0 +1,33 @@
|
||||
import { DEFAULT_STATUS_PAGE_THEME, DEFAULT_STATUS_PAGE_THEME_MODE, type StatusPage } from "@/domain/status-pages/status-page.type.js";
|
||||
|
||||
/**
|
||||
* When STATUS_PAGE_THEMES_ENABLED is false, the controller drops incoming
|
||||
* theme fields before persisting so an admin client can't accidentally store
|
||||
* a value the public page won't honour. Whatever a caller sends here for
|
||||
* `theme` / `themeMode` is silently ignored — the rest of the body is
|
||||
* preserved verbatim.
|
||||
*
|
||||
* The strip is silent (not a 400) because the kill switch is an ops-side
|
||||
* concern: API clients should not have to know whether the operator has
|
||||
* disabled themes today, only that their saved value won't take effect.
|
||||
* If we later decide we'd rather fail loudly, change this to throw a
|
||||
* 400 AppError; downstream consumers already handle it.
|
||||
*/
|
||||
export const withoutThemeFields = (body: Record<string, unknown>): Record<string, unknown> => {
|
||||
const rest = { ...body };
|
||||
delete rest.theme;
|
||||
delete rest.themeMode;
|
||||
return rest;
|
||||
};
|
||||
|
||||
/**
|
||||
* When STATUS_PAGE_THEMES_ENABLED is false, callers reading a status page
|
||||
* should see the canonical default theme / mode regardless of what's stored
|
||||
* in Mongo. Whatever the user previously chose stays in the DB so flipping
|
||||
* the flag back on resurfaces their selection.
|
||||
*/
|
||||
export const applyDefaultTheme = (statusPage: StatusPage): StatusPage => ({
|
||||
...statusPage,
|
||||
theme: DEFAULT_STATUS_PAGE_THEME,
|
||||
themeMode: DEFAULT_STATUS_PAGE_THEME_MODE,
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { ITagsService } from "@/domain/tags/tag.service.js";
|
||||
import { requireTeamId } from "./controllerUtils.js";
|
||||
import {
|
||||
createTagBodyValidation,
|
||||
editTagBodyValidation,
|
||||
getTagByIdParamValidation,
|
||||
editTagParamValidation,
|
||||
deleteTagParamValidation,
|
||||
} from "@/api/validation/index.js";
|
||||
|
||||
export interface ITagsController {
|
||||
createTag: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getTagById: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
getTagsByTeamId: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
editTag: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
deleteTag: (req: Request, res: Response, next: NextFunction) => Promise<Response | void>;
|
||||
}
|
||||
|
||||
class TagsController implements ITagsController {
|
||||
constructor(private tagsService: ITagsService) {}
|
||||
|
||||
createTag = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const validatedBody = createTagBodyValidation.parse(req.body);
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
|
||||
const tag = await this.tagsService.createTag(validatedBody, teamId);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Tag created successfully",
|
||||
data: tag,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
getTagById = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const { id: tagId } = getTagByIdParamValidation.parse(req.params);
|
||||
const tag = await this.tagsService.getTag(tagId, teamId);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Tag retrieved successfully",
|
||||
data: tag,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
getTagsByTeamId = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const tags = await this.tagsService.getTagsByTeamId(teamId);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Tags retrieved successfully",
|
||||
data: tags,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
editTag = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const { id: tagId } = editTagParamValidation.parse(req.params);
|
||||
const validatedBody = editTagBodyValidation.parse(req.body);
|
||||
const updatedTag = await this.tagsService.updateTag(tagId, teamId, validatedBody);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Tag updated successfully",
|
||||
data: updatedTag,
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
deleteTag = async (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const teamId = requireTeamId(req.user?.teamId);
|
||||
const { id: tagId } = deleteTagParamValidation.parse(req.params);
|
||||
await this.tagsService.deleteTag(tagId, teamId);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Tag deleted successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default TagsController;
|
||||
Reference in new issue
Block a user