Merge branch 'develop' of https://github.com/bluewave-labs/bluewave-uptime into fix/PasswordRequirements
This commit is contained in:
commit
3a19920298
27 files changed
+521
-337
No files matched your search
Generated
+3
-3
@@ -3568,9 +3568,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-refresh": {
|
||||
"version": "0.4.13",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.13.tgz",
|
||||
"integrity": "sha512-f1EppwrpJRWmqDTyvAyomFVDYRtrS7iTEqv3nokETnMiMzs2SSTmKRTACce4O2p4jYyowiSMvpdwC/RLcMFhuQ==",
|
||||
"version": "0.4.14",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.14.tgz",
|
||||
"integrity": "sha512-aXvzCTK7ZBv1e7fahFuR3Z/fyQQSIQ711yPgYRj+Oj64tyTgO4iQIDmYXDBqvSWQ/FA4OSCsXOStlF+noU0/NA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
|
||||
@@ -12,6 +12,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) =
|
||||
aria-describedby={ariaDescribedBy}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onClick={(e)=>e.stopPropagation()}
|
||||
>
|
||||
<Stack
|
||||
gap={theme.spacing(2)}
|
||||
|
||||
@@ -181,6 +181,8 @@ function Sidebar() {
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(4)}
|
||||
onClick={() => window.open("https://github.com/bluewave-labs/bluewave-uptime", "_blank", "noreferrer")}
|
||||
sx={{ cursor: "pointer" }}
|
||||
>
|
||||
<Stack
|
||||
justifyContent="center"
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
const PORT = 5000;
|
||||
|
||||
const connectDbAndRunServer = async (app, db) => {
|
||||
try {
|
||||
await db.connect();
|
||||
app.listen(PORT, () => {
|
||||
console.log(`server started on port:${PORT}`);
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Failed to connect to DB");
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
export { connectDbAndRunServer };
|
||||
@@ -79,10 +79,10 @@ const registerUser = async (req, res, next) => {
|
||||
}
|
||||
|
||||
const newUser = await req.db.insertUser({ ...req.body }, req.file);
|
||||
|
||||
logger.info(successMessages.AUTH_CREATE_USER, {
|
||||
logger.info({
|
||||
message: successMessages.AUTH_CREATE_USER,
|
||||
service: SERVICE_NAME,
|
||||
userId: newUser._id,
|
||||
details: newUser._id,
|
||||
});
|
||||
|
||||
const userForToken = { ...newUser._doc };
|
||||
@@ -102,9 +102,11 @@ const registerUser = async (req, res, next) => {
|
||||
"Welcome to Uptime Monitor"
|
||||
)
|
||||
.catch((error) => {
|
||||
logger.error("Error sending welcome email", {
|
||||
logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
error: error.message,
|
||||
method: "registerUser",
|
||||
stack: error.stack,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -279,10 +281,10 @@ const editUser = async (req, res, next) => {
|
||||
const user = await req.db.getUserByEmail(email);
|
||||
// Compare passwords
|
||||
const match = await user.comparePassword(req.body.password);
|
||||
// If not a match, throw a 403
|
||||
// If not a match, throw a 401
|
||||
if (!match) {
|
||||
const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD);
|
||||
error.status = 403;
|
||||
error.status = 401;
|
||||
next(error);
|
||||
return;
|
||||
}
|
||||
@@ -346,32 +348,26 @@ const requestRecovery = async (req, res, next) => {
|
||||
try {
|
||||
const { email } = req.body;
|
||||
const user = await req.db.getUserByEmail(email);
|
||||
if (user) {
|
||||
const recoveryToken = await req.db.requestRecoveryToken(req, res);
|
||||
const name = user.firstName;
|
||||
const email = req.body.email;
|
||||
const { clientHost } = req.settingsService.getSettings();
|
||||
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
|
||||
|
||||
const msgId = await req.emailService.buildAndSendEmail(
|
||||
"passwordResetTemplate",
|
||||
{
|
||||
name,
|
||||
email,
|
||||
url,
|
||||
},
|
||||
const recoveryToken = await req.db.requestRecoveryToken(req, res);
|
||||
const name = user.firstName;
|
||||
const { clientHost } = req.settingsService.getSettings();
|
||||
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
|
||||
const msgId = await req.emailService.buildAndSendEmail(
|
||||
"passwordResetTemplate",
|
||||
{
|
||||
name,
|
||||
email,
|
||||
"Bluewave Uptime Password Reset"
|
||||
);
|
||||
url,
|
||||
},
|
||||
email,
|
||||
"Bluewave Uptime Password Reset"
|
||||
);
|
||||
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN,
|
||||
data: msgId,
|
||||
});
|
||||
} else {
|
||||
throw new Error(errorMessages.FRIENDLY_ERROR);
|
||||
}
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN,
|
||||
data: msgId,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "recoveryRequestController"));
|
||||
}
|
||||
@@ -430,7 +426,6 @@ const resetPassword = async (req, res, next) => {
|
||||
}
|
||||
try {
|
||||
const user = await req.db.resetPassword(req, res);
|
||||
|
||||
const appSettings = await req.settingsService.getSettings();
|
||||
const token = issueToken(user._doc, tokenType.ACCESS_TOKEN, appSettings);
|
||||
res.status(200).json({
|
||||
@@ -460,11 +455,6 @@ const deleteUser = async (req, res, next) => {
|
||||
|
||||
// Check if the user exists
|
||||
const user = await req.db.getUserByEmail(email);
|
||||
if (!user) {
|
||||
next(new Error(errorMessages.DB_USER_NOT_FOUND));
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Find all the monitors associated with the team ID if superadmin
|
||||
|
||||
const result = await req.db.getMonitorsByTeamId({
|
||||
|
||||
@@ -53,9 +53,11 @@ const issueInvitation = async (req, res, next) => {
|
||||
"Welcome to Uptime Monitor"
|
||||
)
|
||||
.catch((error) => {
|
||||
logger.error("Error sending invite email", {
|
||||
logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
error: error.message,
|
||||
method: "issueInvitation",
|
||||
stack: error.stack,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
getMonitorByIdQueryValidation,
|
||||
getMonitorsByTeamIdValidation,
|
||||
createMonitorBodyValidation,
|
||||
getMonitorURLByQueryValidation,
|
||||
getMonitorURLByQueryValidation,
|
||||
editMonitorBodyValidation,
|
||||
getMonitorsAndSummaryByTeamIdParamValidation,
|
||||
getMonitorsAndSummaryByTeamIdQueryValidation,
|
||||
@@ -270,32 +270,32 @@ const createMonitor = async (req, res, next) => {
|
||||
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
|
||||
*/
|
||||
const checkEndpointResolution = async (req, res, next) => {
|
||||
try {
|
||||
try {
|
||||
await getMonitorURLByQueryValidation.validateAsync(req.query);
|
||||
} catch (error) {
|
||||
next(handleValidationError(error, SERVICE_NAME));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let { monitorURL } = req.query;
|
||||
monitorURL = new URL(monitorURL);
|
||||
await new Promise((resolve, reject) => {
|
||||
dns.resolve(monitorURL.hostname, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: `URL resolved successfully`,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "checkEndpointResolution"));
|
||||
}
|
||||
}
|
||||
try {
|
||||
let { monitorURL } = req.query;
|
||||
monitorURL = new URL(monitorURL);
|
||||
await new Promise((resolve, reject) => {
|
||||
dns.resolve(monitorURL.hostname, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: `URL resolved successfully`,
|
||||
});
|
||||
} catch (error) {
|
||||
next(handleError(error, SERVICE_NAME, "checkEndpointResolution"));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Deletes a monitor by its ID and also deletes associated checks, alerts, and notifications.
|
||||
@@ -325,14 +325,12 @@ const deleteMonitor = async (req, res, next) => {
|
||||
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
|
||||
await req.db.deleteNotificationsByMonitorId(monitor._id);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
|
||||
{
|
||||
method: "deleteMonitor",
|
||||
service: SERVICE_NAME,
|
||||
error: error.message,
|
||||
}
|
||||
);
|
||||
logger.error({
|
||||
message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
|
||||
service: SERVICE_NAME,
|
||||
method: "deleteMonitor",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
return res.status(200).json({ success: true, msg: successMessages.MONITOR_DELETE });
|
||||
} catch (error) {
|
||||
@@ -365,12 +363,12 @@ const deleteAllMonitors = async (req, res, next) => {
|
||||
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
|
||||
await req.db.deleteNotificationsByMonitorId(monitor._id);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
|
||||
{
|
||||
method: "deleteAllMonitors",
|
||||
}
|
||||
);
|
||||
logger.error({
|
||||
message: `Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
|
||||
service: SERVICE_NAME,
|
||||
method: "deleteAllMonitors",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -517,7 +515,7 @@ export {
|
||||
getMonitorsAndSummaryByTeamId,
|
||||
getMonitorsByTeamId,
|
||||
createMonitor,
|
||||
checkEndpointResolution,
|
||||
checkEndpointResolution,
|
||||
deleteMonitor,
|
||||
deleteAllMonitors,
|
||||
editMonitor,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import mongoose from "mongoose";
|
||||
import logger from "../../utils/logger.js";
|
||||
const AuditSchema = mongoose.Schema({
|
||||
id: { type: String, required: true },
|
||||
title: { type: String, required: true },
|
||||
@@ -90,19 +91,22 @@ PageSpeedCheck.pre("save", async function (next) {
|
||||
const monitor = await mongoose.model("Monitor").findById(this.monitorId);
|
||||
if (monitor && monitor.status !== this.status) {
|
||||
if (monitor.status === true && this.status === false) {
|
||||
// TODO issue alert
|
||||
console.log("Monitor went down");
|
||||
logger.info({ message: "Monitor went down", monitorId: monitor._id });
|
||||
}
|
||||
|
||||
if (monitor.status === false && this.status === true) {
|
||||
// TODO issue alert
|
||||
console.log("Monitor went up");
|
||||
logger.info({ message: "Monitor went up", monitorId: monitor._id });
|
||||
}
|
||||
monitor.status = this.status;
|
||||
await monitor.save();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.error({
|
||||
message: error.message,
|
||||
service: "PageSpeedCheck",
|
||||
method: "pre-save",
|
||||
stack: error.stack,
|
||||
});
|
||||
} finally {
|
||||
next();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import mongoose from "mongoose";
|
||||
import bcrypt from "bcrypt";
|
||||
import logger from "../../utils/logger.js";
|
||||
|
||||
const UserSchema = mongoose.Schema(
|
||||
{
|
||||
@@ -81,7 +82,9 @@ UserSchema.methods.comparePassword = async function (submittedPassword) {
|
||||
const User = mongoose.model("User", UserSchema);
|
||||
|
||||
User.init().then(() => {
|
||||
console.log("User model initialized");
|
||||
logger.info({
|
||||
message: "User model initialized",
|
||||
});
|
||||
});
|
||||
|
||||
export default mongoose.model("User", UserSchema);
|
||||
@@ -1,6 +1,8 @@
|
||||
import mongoose from "mongoose";
|
||||
import UserModel from "../models/User.js";
|
||||
import AppSettings from "../models/AppSettings.js";
|
||||
import logger from "../../utils/logger.js";
|
||||
const SERVICE_NAME = "MongoDB";
|
||||
|
||||
//****************************************
|
||||
// DB Connection
|
||||
@@ -17,14 +19,34 @@ const connect = async () => {
|
||||
appSettings = new AppSettings({});
|
||||
await appSettings.save();
|
||||
}
|
||||
|
||||
console.log("Connected to MongoDB");
|
||||
logger.info({ message: "Connected to MongoDB" });
|
||||
} catch (error) {
|
||||
console.error("Failed to connect to MongoDB");
|
||||
logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "connect",
|
||||
stack: error.stack,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const disconnect = async () => {
|
||||
try {
|
||||
logger.info({ message: "Disconnecting from MongoDB" });
|
||||
await mongoose.disconnect();
|
||||
logger.info({ message: "Disconnected from MongoDB" });
|
||||
return;
|
||||
} catch (error) {
|
||||
logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "disconnect",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const checkSuperadmin = async (req, res) => {
|
||||
try {
|
||||
const superAdmin = await UserModel.findOne({ role: "superadmin" });
|
||||
@@ -148,6 +170,7 @@ import { getAppSettings, updateAppSettings } from "./modules/settingsModule.js";
|
||||
|
||||
export default {
|
||||
connect,
|
||||
disconnect,
|
||||
insertUser,
|
||||
getUserByEmail,
|
||||
updateUser,
|
||||
|
||||
@@ -30,9 +30,11 @@ const createCheck = async (checkData) => {
|
||||
const monitor = await Monitor.findById(monitorId);
|
||||
|
||||
if (!monitor) {
|
||||
logger.error("Monitor not found", {
|
||||
logger.error({
|
||||
message: "Monitor not found",
|
||||
service: SERVICE_NAME,
|
||||
monitorId,
|
||||
method: "createCheck",
|
||||
details: `monitor ID: ${monitorId}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -77,7 +79,11 @@ const getChecksCount = async (req) => {
|
||||
checksQuery.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
console.log("default");
|
||||
logger.warn({
|
||||
message: "invalid filter",
|
||||
service: SERVICE_NAME,
|
||||
method: "getChecksCount",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -110,7 +116,7 @@ const getChecks = async (req) => {
|
||||
if (dateRange !== undefined) {
|
||||
checksQuery.createdAt = { $gte: dateRangeLookup[dateRange] };
|
||||
}
|
||||
// Fitler checks by status
|
||||
// Filter checks by status
|
||||
if (filter !== undefined) {
|
||||
checksQuery.status = false;
|
||||
switch (filter) {
|
||||
@@ -122,7 +128,11 @@ const getChecks = async (req) => {
|
||||
checksQuery.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
console.log("default");
|
||||
logger.warn({
|
||||
message: "invalid filter",
|
||||
service: SERVICE_NAME,
|
||||
method: "getChecks",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -170,7 +180,11 @@ const getTeamChecks = async (req) => {
|
||||
checksQuery.statusCode = 5000;
|
||||
break;
|
||||
default:
|
||||
console.log("default");
|
||||
logger.warn({
|
||||
message: "invalid filter",
|
||||
service: SERVICE_NAME,
|
||||
method: "getTeamChecks",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -248,9 +262,11 @@ const updateChecksTTL = async (teamId, ttl) => {
|
||||
try {
|
||||
await Check.collection.dropIndex("expiry_1");
|
||||
} catch (error) {
|
||||
logger.error("Failed to drop index", {
|
||||
logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "updateChecksTTL",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -202,7 +202,6 @@ const deleteMaintenanceWindowByUserId = async (userId) => {
|
||||
};
|
||||
|
||||
const editMaintenanceWindowById = async (maintenanceWindowId, maintenanceWindowData) => {
|
||||
console.log(maintenanceWindowData);
|
||||
try {
|
||||
const editedMaintenanceWindow = MaintenanceWindow.findByIdAndUpdate(
|
||||
maintenanceWindowId,
|
||||
|
||||
@@ -68,7 +68,7 @@ const insertUser = async (userData, imageFile) => {
|
||||
const getUserByEmail = async (email) => {
|
||||
try {
|
||||
// Need the password to be able to compare, removed .select()
|
||||
// We can strip the hash before returing the user
|
||||
// We can strip the hash before returning the user
|
||||
const user = await UserModel.findOne({ email: email }).select("-profileImage");
|
||||
if (user) {
|
||||
return user;
|
||||
|
||||
+31
-56
@@ -17,7 +17,6 @@ import maintenanceWindowRouter from "./routes/maintenanceWindowRoute.js";
|
||||
import settingsRouter from "./routes/settingsRoute.js";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
import { connectDbAndRunServer } from "./configs/db.js";
|
||||
import queueRouter from "./routes/queueRoute.js";
|
||||
|
||||
//JobQueue service and dependencies
|
||||
@@ -51,31 +50,11 @@ const __dirname = path.dirname(__filename);
|
||||
const openApiSpec = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, "openapi.json"), "utf8")
|
||||
);
|
||||
|
||||
const PORT = 5000;
|
||||
|
||||
// Need to wrap server setup in a function to handle async nature of JobQueue
|
||||
const startApp = async () => {
|
||||
// **************************
|
||||
// Here is where we can swap out DBs easily. Spin up a mongoDB instance and try it out.
|
||||
// Simply comment out the FakeDB and uncomment the MongoDB or vice versa.
|
||||
// We can easily swap between any type of data source as long as the methods are implemented
|
||||
//
|
||||
// FakeDB
|
||||
// const db = require("./db/FakeDb");
|
||||
//
|
||||
// MongoDB
|
||||
// const db = require("./db/MongoDB");
|
||||
//
|
||||
// **************************
|
||||
// const DB_TYPE = {
|
||||
// MongoDB: async () => (await import("./db/mongo/MongoDB.js")).default,
|
||||
// FakedDB: async () => (await import("./db/FakeDb.js")).default,
|
||||
// };
|
||||
|
||||
// const db = DB_TYPE[process.env.DB_TYPE]
|
||||
// ? DB_TYPE[process.env.DB_TYPE]()
|
||||
// : require("./db/FakeDb");
|
||||
|
||||
// const db = DB_TYPE.MongoDB();
|
||||
|
||||
const app = express();
|
||||
|
||||
// middlewares
|
||||
@@ -85,12 +64,8 @@ const startApp = async () => {
|
||||
);
|
||||
app.use(express.json());
|
||||
app.use(helmet());
|
||||
// **************************
|
||||
// Make DB accessible anywhere we have a Request object
|
||||
// By adding the DB to the request object, we can access it in any route
|
||||
// Thus we do not need to import it in every route file, and we can easily swap out DBs as there is only one place to change it
|
||||
// Same applies for JobQueue and emailService
|
||||
// **************************
|
||||
|
||||
// Add db, jobQueue, emailService, and settingsService to request object for easy access
|
||||
app.use((req, res, next) => {
|
||||
req.db = db;
|
||||
req.jobQueue = jobQueue;
|
||||
@@ -114,27 +89,15 @@ const startApp = async () => {
|
||||
//health check
|
||||
app.use("/api/v1/healthy", (req, res) => {
|
||||
try {
|
||||
logger.info("Checking Health of the server.");
|
||||
logger.info({ message: "Checking Health of the server." });
|
||||
return res.status(200).json({ message: "Healthy" });
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.use("/api/v1/mail", async (req, res) => {
|
||||
try {
|
||||
const id = await req.emailService.buildAndSendEmail(
|
||||
"welcomeEmailTemplate",
|
||||
{
|
||||
name: "Alex",
|
||||
},
|
||||
"ajhollid@gmail.com",
|
||||
"Welcome"
|
||||
);
|
||||
res.status(200).json({ success: true, msg: "Email sent", data: id });
|
||||
} catch (error) {
|
||||
logger.error(error.message);
|
||||
logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "healthCheck",
|
||||
stack: error.stack,
|
||||
});
|
||||
return res.status(500).json({ message: error.message });
|
||||
}
|
||||
});
|
||||
@@ -146,7 +109,10 @@ const startApp = async () => {
|
||||
app.use(handleErrors);
|
||||
|
||||
// Create services
|
||||
await connectDbAndRunServer(app, db);
|
||||
await db.connect();
|
||||
app.listen(PORT, () => {
|
||||
logger.info({ message: `server started on port:${PORT}` });
|
||||
});
|
||||
const settingsService = new SettingsService(AppSettings);
|
||||
|
||||
await settingsService.loadSettings();
|
||||
@@ -171,18 +137,21 @@ const startApp = async () => {
|
||||
|
||||
const cleanup = async () => {
|
||||
if (cleaningUp) {
|
||||
console.log("Already cleaning up");
|
||||
logger.warn({ message: "Already cleaning up" });
|
||||
return;
|
||||
}
|
||||
cleaningUp = true;
|
||||
try {
|
||||
console.log("Shutting down gracefully");
|
||||
logger.info({ message: "shutting down gracefully" });
|
||||
await jobQueue.obliterate();
|
||||
console.log("Finished cleanup");
|
||||
await db.disconnect();
|
||||
logger.info({ message: "shut down gracefully" });
|
||||
} catch (error) {
|
||||
logger.error(errorMessages.JOB_QUEUE_DELETE_JOB, {
|
||||
logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
errorMsg: error.message,
|
||||
method: "cleanup",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
process.exit(0);
|
||||
@@ -193,5 +162,11 @@ const startApp = async () => {
|
||||
};
|
||||
|
||||
startApp().catch((error) => {
|
||||
console.log(error);
|
||||
logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "startApp",
|
||||
stack: error.stack,
|
||||
});
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -5,9 +5,11 @@ const handleErrors = (error, req, res, next) => {
|
||||
const status = error.status || 500;
|
||||
const message = error.message || errorMessages.FRIENDLY_ERROR;
|
||||
const service = error.service || errorMessages.UNKNOWN_SERVICE;
|
||||
logger.error(error.message, {
|
||||
logger.error({
|
||||
message: message,
|
||||
service: service,
|
||||
method: error.method,
|
||||
stack: error.stack,
|
||||
});
|
||||
res.status(status).json({ success: false, msg: message });
|
||||
};
|
||||
|
||||
@@ -10,8 +10,10 @@ const verifyOwnership = (Model, paramName) => {
|
||||
const doc = await Model.findById(documentId);
|
||||
//If the document is not found, return a 404 error
|
||||
if (!doc) {
|
||||
logger.error(errorMessages.VERIFY_OWNER_NOT_FOUND, {
|
||||
logger.error({
|
||||
message: errorMessages.VERIFY_OWNER_NOT_FOUND,
|
||||
service: SERVICE_NAME,
|
||||
method: "verifyOwnership",
|
||||
});
|
||||
const error = new Error(errorMessages.VERIFY_OWNER_NOT_FOUND);
|
||||
error.status = 404;
|
||||
|
||||
@@ -37,8 +37,12 @@ const verifySuperAdmin = (req, res, next) => {
|
||||
|
||||
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
|
||||
if (err) {
|
||||
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
|
||||
logger.error({
|
||||
message: err.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "verifySuperAdmin",
|
||||
stack: err.stack,
|
||||
details: errorMessages.INVALID_AUTH_TOKEN,
|
||||
});
|
||||
return res
|
||||
.status(401)
|
||||
@@ -46,8 +50,11 @@ const verifySuperAdmin = (req, res, next) => {
|
||||
}
|
||||
|
||||
if (decoded.role.includes("superadmin") === false) {
|
||||
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
|
||||
logger.error({
|
||||
message: errorMessages.INVALID_AUTH_TOKEN,
|
||||
service: SERVICE_NAME,
|
||||
method: "verifySuperAdmin",
|
||||
stack: err.stack,
|
||||
});
|
||||
return res.status(401).json({ success: false, msg: errorMessages.UNAUTHORIZED });
|
||||
}
|
||||
|
||||
@@ -44,9 +44,11 @@ class EmailService {
|
||||
const templateContent = this.fs.readFileSync(templatePath, "utf8");
|
||||
return this.compile(templateContent);
|
||||
} catch (error) {
|
||||
this.logger.error("Error loading Email templates", {
|
||||
error,
|
||||
service: this.SERVICE_NAME,
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "loadTemplate",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -102,9 +104,11 @@ class EmailService {
|
||||
const html = await this.mjml2html(mjml);
|
||||
return html.html;
|
||||
} catch (error) {
|
||||
this.logger.error("Error building Email HTML", {
|
||||
error,
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "buildAndSendEmail",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -118,9 +122,11 @@ class EmailService {
|
||||
});
|
||||
return info;
|
||||
} catch (error) {
|
||||
this.logger.error("Error sending Email", {
|
||||
error,
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "sendEmail",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
+35
-25
@@ -107,16 +107,19 @@ class JobQueue {
|
||||
if (!maintenanceWindowActive) {
|
||||
await this.networkService.getStatus(job);
|
||||
} else {
|
||||
this.logger.info(`Monitor ${monitorId} is in maintenance window`, {
|
||||
this.logger.info({
|
||||
message: `Monitor ${monitorId} is in maintenance window`,
|
||||
service: SERVICE_NAME,
|
||||
monitorId,
|
||||
method: "createWorker",
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Error processing job ${job.id}: ${error.message}`, {
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
jobId: job.id,
|
||||
error: error,
|
||||
method: "createWorker",
|
||||
details: `Error processing job ${job.id}: ${error.message}`,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
},
|
||||
@@ -197,14 +200,15 @@ class JobQueue {
|
||||
while (workersToRemove > 0 && this.workers.length > 5) {
|
||||
const worker = this.workers.pop();
|
||||
workersToRemove--;
|
||||
try {
|
||||
await worker.close();
|
||||
} catch (error) {
|
||||
await worker.close().catch((error) => {
|
||||
// Catch the error instead of throwing it
|
||||
this.logger.error(errorMessages.JOB_QUEUE_WORKER_CLOSE, {
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "scaleWorkers",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -256,7 +260,7 @@ class JobQueue {
|
||||
*/
|
||||
async addJob(jobName, payload) {
|
||||
try {
|
||||
console.log("Adding job", payload?.url ?? "No URL");
|
||||
this.logger.info({ message: `Adding job ${payload?.url ?? "No URL"}` });
|
||||
// Execute job immediately
|
||||
await this.queue.add(jobName, payload);
|
||||
await this.queue.add(jobName, payload, {
|
||||
@@ -282,20 +286,24 @@ class JobQueue {
|
||||
*/
|
||||
async deleteJob(monitor) {
|
||||
try {
|
||||
const deleted = await this.queue.removeRepeatable(monitor._id, {
|
||||
const wasDeleted = await this.queue.removeRepeatable(monitor._id, {
|
||||
every: monitor.interval,
|
||||
});
|
||||
if (deleted) {
|
||||
this.logger.info(successMessages.JOB_QUEUE_DELETE_JOB, {
|
||||
if (wasDeleted === true) {
|
||||
this.logger.info({
|
||||
message: successMessages.JOB_QUEUE_DELETE_JOB,
|
||||
service: SERVICE_NAME,
|
||||
jobId: monitor.id,
|
||||
method: "deleteJob",
|
||||
details: `Deleted job ${monitor._id}`,
|
||||
});
|
||||
const workerStats = await this.getWorkerStats();
|
||||
await this.scaleWorkers(workerStats);
|
||||
} else {
|
||||
this.logger.error(errorMessages.JOB_QUEUE_DELETE_JOB, {
|
||||
this.logger.error({
|
||||
message: errorMessages.JOB_QUEUE_DELETE_JOB,
|
||||
service: SERVICE_NAME,
|
||||
jobId: monitor.id,
|
||||
method: "deleteJob",
|
||||
details: `Failed to delete job ${monitor._id}`,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -315,12 +323,16 @@ class JobQueue {
|
||||
delayed: await this.queue.getDelayedCount(),
|
||||
repeatableJobs: (await this.queue.getRepeatableJobs()).length,
|
||||
};
|
||||
console.log(metrics);
|
||||
this.logger.info({
|
||||
message: metrics,
|
||||
});
|
||||
return metrics;
|
||||
} catch (error) {
|
||||
this.logger.error("Failed to retrieve job queue metrics", {
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
errorMsg: error.message,
|
||||
method: "getMetrics",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -332,7 +344,7 @@ class JobQueue {
|
||||
async obliterate() {
|
||||
try {
|
||||
let metrics = await this.getMetrics();
|
||||
console.log(metrics);
|
||||
this.logger.info({ message: metrics });
|
||||
await this.queue.pause();
|
||||
const jobs = await this.getJobs();
|
||||
|
||||
@@ -348,10 +360,8 @@ class JobQueue {
|
||||
|
||||
await this.queue.obliterate();
|
||||
metrics = await this.getMetrics();
|
||||
console.log(metrics);
|
||||
this.logger.info(successMessages.JOB_QUEUE_OBLITERATE, {
|
||||
service: SERVICE_NAME,
|
||||
});
|
||||
this.logger.info({ message: metrics });
|
||||
this.logger.info({ message: successMessages.JOB_QUEUE_OBLITERATE });
|
||||
return true;
|
||||
} catch (error) {
|
||||
error.service === undefined ? (error.service = SERVICE_NAME) : null;
|
||||
|
||||
@@ -56,10 +56,12 @@ class NetworkService {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error.message, {
|
||||
method: "handleNotification",
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: this.SERVICE_NAME,
|
||||
monitorId: monitor._id,
|
||||
method: "handleNotification",
|
||||
details: `notification error for monitor: ${monitor._id}`,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -79,19 +81,18 @@ class NetworkService {
|
||||
try {
|
||||
monitor = await this.db.getMonitorById(_id);
|
||||
} catch (error) {
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "handleStatusUpdate",
|
||||
stack: error.stack,
|
||||
details: `monitor lookup error for monitor: ${_id}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, try to update monitor status
|
||||
try {
|
||||
if (monitor === null || monitor === undefined) {
|
||||
this.logger.error(`Null Monitor: ${_id}`, {
|
||||
method: "handleStatusUpdate",
|
||||
service: this.SERVICE_NAME,
|
||||
jobId: job.id,
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (monitor.status === undefined || monitor.status !== isAlive) {
|
||||
const oldStatus = monitor.status;
|
||||
monitor.status = isAlive;
|
||||
@@ -102,10 +103,12 @@ class NetworkService {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(error.message, {
|
||||
method: "handleStatusUpdate",
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: this.SERVICE_NAME,
|
||||
jobId: job.id,
|
||||
method: "handleStatusUpdate",
|
||||
stack: error.stack,
|
||||
details: `status update error for monitor: ${_id}`,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -391,11 +394,12 @@ class NetworkService {
|
||||
case this.TYPE_HARDWARE:
|
||||
return await this.handleHardware(job);
|
||||
default:
|
||||
this.logger.error(`Unsupported type: ${job.data.type}`, {
|
||||
this.logger.error({
|
||||
message: `Unsupported type: ${job.data.type}`,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "getStatus",
|
||||
jobId: job.id,
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -417,11 +421,12 @@ class NetworkService {
|
||||
}
|
||||
throw new Error();
|
||||
} catch (error) {
|
||||
this.logger.error(`Error writing check for ${data.monitorId}`, {
|
||||
this.logger.error({
|
||||
message: error.message,
|
||||
service: this.SERVICE_NAME,
|
||||
method: "logAndStoreCheck",
|
||||
monitorId: data.monitorId,
|
||||
error: error,
|
||||
details: `Error writing check for ${data.monitorId}`,
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +182,7 @@ describe("Auth Controller - registerUser", () => {
|
||||
req.emailService.buildAndSendEmail.rejects(new Error("emailService error"));
|
||||
await registerUser(req, res, next);
|
||||
expect(logger.error.calledOnce).to.be.true;
|
||||
expect(logger.error.firstCall.args[1].error).to.equal("emailService error");
|
||||
expect(logger.error.firstCall.args[0].message).to.equal("emailService error");
|
||||
});
|
||||
it("should return a success message and data if all operations are successful", async () => {
|
||||
const user = { _id: "123" };
|
||||
@@ -467,7 +467,7 @@ describe("Auth Controller - editUser", async () => {
|
||||
});
|
||||
await editUser(req, res, next);
|
||||
expect(next.firstCall.args[0]).to.be.an("error");
|
||||
expect(next.firstCall.args[0].status).to.equal(403);
|
||||
expect(next.firstCall.args[0].status).to.equal(401);
|
||||
expect(next.firstCall.args[0].message).to.equal(
|
||||
errorMessages.AUTH_INCORRECT_PASSWORD
|
||||
);
|
||||
@@ -812,9 +812,9 @@ describe("Auth Controller - deleteUser", () => {
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
it("should return 404 if user is not found", async () => {
|
||||
it("should throw an error if user is not found", async () => {
|
||||
jwt.decode.returns({ email: "test@example.com" });
|
||||
req.db.getUserByEmail.resolves(null);
|
||||
req.db.getUserByEmail.throws(new Error(errorMessages.DB_USER_NOT_FOUND));
|
||||
|
||||
await deleteUser(req, res, next);
|
||||
|
||||
|
||||
@@ -462,38 +462,40 @@ describe("Monitor Controller - createMonitor", () => {
|
||||
describe("Monitor Controllor - checkEndpointResolution", () => {
|
||||
let req, res, next, dnsResolveStub;
|
||||
beforeEach(() => {
|
||||
req = { query: { monitorURL: 'https://example.com' } };
|
||||
req = { query: { monitorURL: "https://example.com" } };
|
||||
res = { status: sinon.stub().returnsThis(), json: sinon.stub() };
|
||||
next = sinon.stub();
|
||||
dnsResolveStub = sinon.stub(dns, 'resolve');
|
||||
dnsResolveStub = sinon.stub(dns, "resolve");
|
||||
});
|
||||
afterEach(() => {
|
||||
dnsResolveStub.restore();
|
||||
});
|
||||
it('should resolve the URL successfully', async () => {
|
||||
it("should resolve the URL successfully", async () => {
|
||||
dnsResolveStub.callsFake((hostname, callback) => callback(null));
|
||||
await checkEndpointResolution(req, res, next);
|
||||
expect(res.status.calledWith(200)).to.be.true;
|
||||
expect(res.json.calledWith({
|
||||
success: true,
|
||||
msg: 'URL resolved successfully',
|
||||
})).to.be.true;
|
||||
expect(
|
||||
res.json.calledWith({
|
||||
success: true,
|
||||
msg: "URL resolved successfully",
|
||||
})
|
||||
).to.be.true;
|
||||
expect(next.called).to.be.false;
|
||||
});
|
||||
it("should return an error if DNS resolution fails", async () => {
|
||||
const dnsError = new Error("DNS resolution failed");
|
||||
dnsError.code = 'ENOTFOUND';
|
||||
dnsError.code = "ENOTFOUND";
|
||||
dnsResolveStub.callsFake((hostname, callback) => callback(dnsError));
|
||||
await checkEndpointResolution(req, res, next);
|
||||
expect(next.calledOnce).to.be.true;
|
||||
const errorPassedToNext = next.getCall(0).args[0];
|
||||
expect(errorPassedToNext).to.be.an.instanceOf(Error);
|
||||
expect(errorPassedToNext.message).to.include('DNS resolution failed');
|
||||
expect(errorPassedToNext.code).to.equal('ENOTFOUND');
|
||||
const errorPassedToNext = next.getCall(0).args[0];
|
||||
expect(errorPassedToNext).to.be.an.instanceOf(Error);
|
||||
expect(errorPassedToNext.message).to.include("DNS resolution failed");
|
||||
expect(errorPassedToNext.code).to.equal("ENOTFOUND");
|
||||
expect(errorPassedToNext.status).to.equal(500);
|
||||
});
|
||||
it('should reject with an error if query validation fails', async () => {
|
||||
req.query.monitorURL = 'invalid-url';
|
||||
it("should reject with an error if query validation fails", async () => {
|
||||
req.query.monitorURL = "invalid-url";
|
||||
await checkEndpointResolution(req, res, next);
|
||||
expect(next.calledOnce).to.be.true;
|
||||
const error = next.getCall(0).args[0];
|
||||
@@ -551,14 +553,9 @@ describe("Monitor Controller - deleteMonitor", () => {
|
||||
req.jobQueue.deleteJob.rejects(error);
|
||||
await deleteMonitor(req, res, next);
|
||||
expect(logger.error.calledOnce).to.be.true;
|
||||
expect(logger.error.firstCall.args[0]).to.equal(
|
||||
expect(logger.error.firstCall.args[0].message).to.equal(
|
||||
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
|
||||
);
|
||||
expect(logger.error.firstCall.args[1]).to.deep.equal({
|
||||
method: "deleteMonitor",
|
||||
service: SERVICE_NAME,
|
||||
error: error.message,
|
||||
});
|
||||
});
|
||||
it("should log an error if deleteChecks throws an error", async () => {
|
||||
const error = new Error("Checks error");
|
||||
@@ -567,14 +564,9 @@ describe("Monitor Controller - deleteMonitor", () => {
|
||||
req.db.deleteChecks.rejects(error);
|
||||
await deleteMonitor(req, res, next);
|
||||
expect(logger.error.calledOnce).to.be.true;
|
||||
expect(logger.error.firstCall.args[0]).to.equal(
|
||||
expect(logger.error.firstCall.args[0].message).to.equal(
|
||||
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
|
||||
);
|
||||
expect(logger.error.firstCall.args[1]).to.deep.equal({
|
||||
method: "deleteMonitor",
|
||||
service: SERVICE_NAME,
|
||||
error: error.message,
|
||||
});
|
||||
});
|
||||
it("should log an error if deletePageSpeedChecksByMonitorId throws an error", async () => {
|
||||
const error = new Error("PageSpeed error");
|
||||
@@ -583,14 +575,9 @@ describe("Monitor Controller - deleteMonitor", () => {
|
||||
req.db.deletePageSpeedChecksByMonitorId.rejects(error);
|
||||
await deleteMonitor(req, res, next);
|
||||
expect(logger.error.calledOnce).to.be.true;
|
||||
expect(logger.error.firstCall.args[0]).to.equal(
|
||||
expect(logger.error.firstCall.args[0].message).to.equal(
|
||||
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
|
||||
);
|
||||
expect(logger.error.firstCall.args[1]).to.deep.equal({
|
||||
method: "deleteMonitor",
|
||||
service: SERVICE_NAME,
|
||||
error: error.message,
|
||||
});
|
||||
});
|
||||
it("should log an error if deleteNotificationsByMonitorId throws an error", async () => {
|
||||
const error = new Error("Notifications error");
|
||||
@@ -599,14 +586,9 @@ describe("Monitor Controller - deleteMonitor", () => {
|
||||
req.db.deleteNotificationsByMonitorId.rejects(error);
|
||||
await deleteMonitor(req, res, next);
|
||||
expect(logger.error.calledOnce).to.be.true;
|
||||
expect(logger.error.firstCall.args[0]).to.equal(
|
||||
expect(logger.error.firstCall.args[0].message).to.equal(
|
||||
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`
|
||||
);
|
||||
expect(logger.error.firstCall.args[1]).to.deep.equal({
|
||||
method: "deleteMonitor",
|
||||
service: SERVICE_NAME,
|
||||
error: error.message,
|
||||
});
|
||||
});
|
||||
it("should return success message if all operations succeed", async () => {
|
||||
const monitor = { name: "test_monitor", _id: "123" };
|
||||
@@ -681,18 +663,7 @@ describe("Monitor Controller - deleteAllMonitors", () => {
|
||||
expect(next.firstCall.args[0]).to.be.an("error");
|
||||
expect(next.firstCall.args[0].message).to.equal("DB error");
|
||||
});
|
||||
it("should log an error if deleteJob throws an error", async () => {
|
||||
const monitors = [{ name: "test_monitor", _id: "123" }];
|
||||
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
|
||||
req.db.deleteAllMonitors.returns({ monitors, deletedCount: 1 });
|
||||
const error = new Error("Job error");
|
||||
req.jobQueue.deleteJob.rejects(error);
|
||||
await deleteAllMonitors(req, res, next);
|
||||
expect(logger.error.calledOnce).to.be.true;
|
||||
expect(logger.error.firstCall.args[0]).to.deep.equal(
|
||||
`Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
|
||||
);
|
||||
});
|
||||
|
||||
it("should log an error if deleteChecks throws an error", async () => {
|
||||
const monitors = [{ name: "test_monitor", _id: "123" }];
|
||||
req.settingsService.getSettings.returns({ jwtSecret: "my_secret" });
|
||||
@@ -701,7 +672,7 @@ describe("Monitor Controller - deleteAllMonitors", () => {
|
||||
req.db.deleteChecks.rejects(error);
|
||||
await deleteAllMonitors(req, res, next);
|
||||
expect(logger.error.calledOnce).to.be.true;
|
||||
expect(logger.error.firstCall.args[0]).to.deep.equal(
|
||||
expect(logger.error.firstCall.args[0].message).to.equal(
|
||||
`Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
|
||||
);
|
||||
});
|
||||
@@ -713,7 +684,7 @@ describe("Monitor Controller - deleteAllMonitors", () => {
|
||||
req.db.deletePageSpeedChecksByMonitorId.rejects(error);
|
||||
await deleteAllMonitors(req, res, next);
|
||||
expect(logger.error.calledOnce).to.be.true;
|
||||
expect(logger.error.firstCall.args[0]).to.deep.equal(
|
||||
expect(logger.error.firstCall.args[0].message).to.equal(
|
||||
`Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
|
||||
);
|
||||
});
|
||||
@@ -725,7 +696,7 @@ describe("Monitor Controller - deleteAllMonitors", () => {
|
||||
req.db.deleteNotificationsByMonitorId.rejects(error);
|
||||
await deleteAllMonitors(req, res, next);
|
||||
expect(logger.error.calledOnce).to.be.true;
|
||||
expect(logger.error.firstCall.args[0]).to.deep.equal(
|
||||
expect(logger.error.firstCall.args[0].message).to.equal(
|
||||
`Error deleting associated records for monitor ${monitors[0]._id} with name ${monitors[0].name}`
|
||||
);
|
||||
});
|
||||
|
||||
@@ -93,14 +93,7 @@ describe("EmailService - Constructor", () => {
|
||||
loggerMock
|
||||
);
|
||||
expect(loggerMock.error.called).to.be.true;
|
||||
const errorCalls = loggerMock.error.getCalls();
|
||||
const errorCall = errorCalls.find(
|
||||
(call) => call.args[0] === "Error loading Email templates"
|
||||
);
|
||||
expect(errorCall).to.not.be.undefined;
|
||||
expect(emailService.settingsService).to.equal(settingsServiceMock);
|
||||
expect(emailService.templateLookup.welcomeEmailTemplate).to.be.undefined;
|
||||
expect(emailService.templateLookup.employeeActivationTemplate).to.be.undefined;
|
||||
expect(loggerMock.error.firstCall.args[0].message).to.equal("File read error");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -182,7 +175,7 @@ describe("EmailService - buildAndSendEmail", () => {
|
||||
"Welcome"
|
||||
);
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.getCall(0).args[0]).to.equal("Error building Email HTML");
|
||||
expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error");
|
||||
});
|
||||
|
||||
it("should log error if sending email fails", async () => {
|
||||
@@ -194,7 +187,7 @@ describe("EmailService - buildAndSendEmail", () => {
|
||||
"Welcome"
|
||||
);
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.getCall(0).args[0]).to.equal("Error sending Email");
|
||||
expect(loggerMock.error.getCall(0).args[0].message).to.equal("SMTP error");
|
||||
});
|
||||
|
||||
it("should log error if both building HTML and sending email fail", async () => {
|
||||
@@ -210,8 +203,8 @@ describe("EmailService - buildAndSendEmail", () => {
|
||||
|
||||
expect(messageId).to.be.undefined;
|
||||
expect(loggerMock.error.calledTwice).to.be.true;
|
||||
expect(loggerMock.error.getCall(0).args[0]).to.equal("Error building Email HTML");
|
||||
expect(loggerMock.error.getCall(1).args[0]).to.equal("Error sending Email");
|
||||
expect(loggerMock.error.getCall(0).args[0].message).to.equal("MJML error");
|
||||
expect(loggerMock.error.getCall(1).args[0].message).to.equal("SMTP error");
|
||||
});
|
||||
|
||||
it("should log an error if buildHtml fails", async () => {});
|
||||
|
||||
@@ -18,7 +18,10 @@ class QueueStub {
|
||||
removeRepeatable(id) {
|
||||
const removedJob = this.jobs.find((job) => job.data._id === id);
|
||||
this.jobs = this.jobs.filter((job) => job.data._id !== id);
|
||||
return removedJob;
|
||||
if (removedJob) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getRepeatableJobs() {
|
||||
@@ -558,10 +561,10 @@ describe("JobQueue", () => {
|
||||
const job = { data: monitor };
|
||||
jobQueue.queue.jobs = [job];
|
||||
await jobQueue.deleteJob(monitor);
|
||||
expect(jobQueue.queue.jobs.length).to.equal(0);
|
||||
expect(logger.info.calledOnce).to.be.true;
|
||||
expect(jobQueue.getWorkerStats.calledOnce).to.be.true;
|
||||
expect(jobQueue.scaleWorkers.calledOnce).to.be.true;
|
||||
// expect(jobQueue.queue.jobs.length).to.equal(0);
|
||||
// expect(logger.info.calledOnce).to.be.true;
|
||||
// expect(jobQueue.getWorkerStats.calledOnce).to.be.true;
|
||||
// expect(jobQueue.scaleWorkers.calledOnce).to.be.true;
|
||||
});
|
||||
it("should log an error if job is not found", async () => {
|
||||
const jobQueue = await JobQueue.createJobQueue(
|
||||
@@ -667,7 +670,7 @@ describe("JobQueue", () => {
|
||||
};
|
||||
await jobQueue.getMetrics();
|
||||
expect(logger.error.calledOnce).to.be.true;
|
||||
expect(logger.error.calledWith("Failed to retrieve job queue metrics")).to.be.true;
|
||||
expect(logger.error.firstCall.args[0].message).to.equal("Error");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -105,13 +105,7 @@ describe("NetworkService - handleNotification", () => {
|
||||
await networkService.handleNotification(monitor, true);
|
||||
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(
|
||||
loggerMock.error.calledWith("Database error", {
|
||||
method: "handleNotification",
|
||||
service: networkService.SERVICE_NAME,
|
||||
monitorId: monitor._id,
|
||||
})
|
||||
).to.be.true;
|
||||
expect(loggerMock.error.firstCall.args[0].message).to.equal("Database error");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -160,13 +154,13 @@ describe("NetworkService - handleStatusUpdate", function () {
|
||||
expect(res).to.be.undefined;
|
||||
});
|
||||
|
||||
it("should log an error if monitor is null", async function () {
|
||||
dbMock.getMonitorById.resolves(null);
|
||||
it("should log an error if finding monitor fails", async function () {
|
||||
dbMock.getMonitorById.throws(new Error("Database error"));
|
||||
|
||||
await networkService.handleStatusUpdate({ data: { _id: "monitor1" } }, true);
|
||||
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith("Null Monitor: monitor1")).to.be.true;
|
||||
expect(loggerMock.error.firstCall.args[0].message).to.equal("Database error");
|
||||
});
|
||||
|
||||
it("should update monitor status if different", async function () {
|
||||
@@ -196,7 +190,7 @@ describe("NetworkService - handleStatusUpdate", function () {
|
||||
await networkService.handleStatusUpdate({ data: { _id: "monitor1" } }, true);
|
||||
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith("Save error")).to.be.true;
|
||||
expect(loggerMock.error.firstCall.args[0].message).to.equal("Save error");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -794,7 +788,9 @@ describe("NetworkService - getStatus", () => {
|
||||
const result = await networkService.getStatus(job);
|
||||
expect(result).to.be.false;
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith(`Unsupported type: unknown`)).to.be.true;
|
||||
expect(loggerMock.error.firstCall.args[0].message).to.equal(
|
||||
"Unsupported type: unknown"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -823,12 +819,14 @@ describe("NetworkService - logAndStoreCheck", async () => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should log an error if `writeToDb` throws an error", async () => {
|
||||
it("should log an error if writeToDb throws an error", async () => {
|
||||
writeToDB.rejects(new Error("Database error"));
|
||||
await networkService.logAndStoreCheck(data, writeToDB);
|
||||
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith(`Error writing check for ${data.monitorId}`)).to.be
|
||||
.true;
|
||||
expect(loggerMock.error.firstCall.args[0].details).to.equal(
|
||||
`Error writing check for ${data.monitorId}`
|
||||
);
|
||||
});
|
||||
|
||||
it("should return the status of the inserted check if successful", async () => {
|
||||
@@ -841,14 +839,17 @@ describe("NetworkService - logAndStoreCheck", async () => {
|
||||
writeToDB.resolves(null);
|
||||
await networkService.logAndStoreCheck(data, writeToDB);
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith(`Error writing check for ${data.monitorId}`)).to.be
|
||||
.true;
|
||||
expect(loggerMock.error.firstCall.args[0].details).to.equal(
|
||||
`Error writing check for ${data.monitorId}`
|
||||
);
|
||||
});
|
||||
it("should thrown an error if the check is not inserted (undefined)", async () => {
|
||||
writeToDB.resolves(undefined);
|
||||
await networkService.logAndStoreCheck(data, writeToDB);
|
||||
|
||||
expect(loggerMock.error.calledOnce).to.be.true;
|
||||
expect(loggerMock.error.calledWith(`Error writing check for ${data.monitorId}`)).to.be
|
||||
.true;
|
||||
expect(loggerMock.error.firstCall.args[0].details).to.equal(
|
||||
`Error writing check for ${data.monitorId}`
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
import sinon from "sinon";
|
||||
import logger from "../../utils/logger.js";
|
||||
import { Logger } from "../../utils/logger.js";
|
||||
import winston from "winston";
|
||||
|
||||
describe("Logger", () => {
|
||||
let infoStub, warnStub, errorStub;
|
||||
|
||||
beforeEach(() => {
|
||||
infoStub = sinon.stub(logger.logger, "info");
|
||||
warnStub = sinon.stub(logger.logger, "warn");
|
||||
errorStub = sinon.stub(logger.logger, "error");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
describe("constructor", () => {
|
||||
let createLoggerStub;
|
||||
|
||||
beforeEach(function () {
|
||||
createLoggerStub = sinon.stub(winston, "createLogger");
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
it("should convert message to JSON string if it is an object", function () {
|
||||
const logMessage = { key: "value" };
|
||||
const expectedMessage = JSON.stringify(logMessage, null, 2);
|
||||
|
||||
createLoggerStub.callsFake((config) => {
|
||||
const consoleTransport = config.transports[0];
|
||||
const logEntry = {
|
||||
level: "info",
|
||||
message: logMessage,
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
const formattedMessage = consoleTransport.format.transform(logEntry);
|
||||
expect(formattedMessage).to.include(expectedMessage);
|
||||
return { log: sinon.spy() };
|
||||
});
|
||||
|
||||
const logger = new Logger();
|
||||
logger.logger.info(logMessage);
|
||||
});
|
||||
});
|
||||
|
||||
describe("info", () => {
|
||||
it("should log an informational message", () => {
|
||||
const config = {
|
||||
message: "Info message",
|
||||
service: "TestService",
|
||||
method: "TestMethod",
|
||||
details: { key: "value" },
|
||||
};
|
||||
|
||||
logger.info(config);
|
||||
|
||||
expect(infoStub.calledOnce).to.be.true;
|
||||
expect(
|
||||
infoStub.calledWith(config.message, {
|
||||
service: config.service,
|
||||
method: config.method,
|
||||
details: config.details,
|
||||
})
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("warn", () => {
|
||||
it("should log a warning message", () => {
|
||||
const config = {
|
||||
message: "Warning message",
|
||||
service: "TestService",
|
||||
method: "TestMethod",
|
||||
details: { key: "value" },
|
||||
};
|
||||
|
||||
logger.warn(config);
|
||||
|
||||
expect(warnStub.calledOnce).to.be.true;
|
||||
expect(
|
||||
warnStub.calledWith(config.message, {
|
||||
service: config.service,
|
||||
method: config.method,
|
||||
details: config.details,
|
||||
})
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe("error", () => {
|
||||
it("should log an error message", () => {
|
||||
const config = {
|
||||
message: "Error message",
|
||||
service: "TestService",
|
||||
method: "TestMethod",
|
||||
details: { key: "value" },
|
||||
stack: "Error stack trace",
|
||||
};
|
||||
|
||||
logger.error(config);
|
||||
|
||||
expect(errorStub.calledOnce).to.be.true;
|
||||
expect(
|
||||
errorStub.calledWith(config.message, {
|
||||
service: config.service,
|
||||
method: config.method,
|
||||
details: config.details,
|
||||
stack: config.stack,
|
||||
})
|
||||
).to.be.true;
|
||||
});
|
||||
});
|
||||
});
|
||||
+89
-21
@@ -1,23 +1,91 @@
|
||||
import winston from "winston";
|
||||
/**
|
||||
* @module
|
||||
* @example
|
||||
* logger.info("Registered a new user!")
|
||||
* logger.warn("User not found!")
|
||||
* logger.error("Cannot save")
|
||||
* @example
|
||||
* "Specify service and ID in the log if applicable."
|
||||
* logger.error("Descriptive Message",{"service":"monitor","monitorId":"123456"})
|
||||
* logger.error("Incorrect Credentials",{"service":"Auth","userId":"654321"})
|
||||
* logger.error("User not found!",{"service":"Auth"})
|
||||
*/
|
||||
const logger = winston.createLogger({
|
||||
level: "info",
|
||||
format: winston.format.combine(winston.format.timestamp(), winston.format.json()),
|
||||
transports: [
|
||||
new winston.transports.Console(),
|
||||
new winston.transports.File({ filename: "app.log" }),
|
||||
],
|
||||
});
|
||||
import { createLogger, format, transports } from "winston";
|
||||
|
||||
class Logger {
|
||||
constructor() {
|
||||
const consoleFormat = format.printf(
|
||||
({ level, message, service, method, details, timestamp }) => {
|
||||
if (message instanceof Object) {
|
||||
message = JSON.stringify(message, null, 2);
|
||||
}
|
||||
let msg = `${timestamp} ${level}:`;
|
||||
service && (msg += ` [${service}]`);
|
||||
method && (msg += `(${method})`);
|
||||
message && (msg += ` ${message}`);
|
||||
details && (msg += ` (details: ${details})`);
|
||||
return msg;
|
||||
}
|
||||
);
|
||||
|
||||
this.logger = createLogger({
|
||||
level: "info",
|
||||
format: format.combine(format.timestamp()),
|
||||
transports: [
|
||||
new transports.Console({
|
||||
format: format.combine(
|
||||
format.colorize(),
|
||||
format.prettyPrint(),
|
||||
format.json(),
|
||||
consoleFormat
|
||||
),
|
||||
}),
|
||||
new transports.File({
|
||||
format: format.combine(format.json()),
|
||||
filename: "app.log",
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Logs an informational message.
|
||||
* @param {Object} config - The configuration object.
|
||||
* @param {string} config.message - The message to log.
|
||||
* @param {string} config.service - The service name.
|
||||
* @param {string} config.method - The method name.
|
||||
* @param {Object} config.details - Additional details.
|
||||
*/
|
||||
info(config) {
|
||||
this.logger.info(config.message, {
|
||||
service: config.service,
|
||||
method: config.method,
|
||||
details: config.details,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a warning message.
|
||||
* @param {Object} config - The configuration object.
|
||||
* @param {string} config.message - The message to log.
|
||||
* @param {string} config.service - The service name.
|
||||
* @param {string} config.method - The method name.
|
||||
* @param {Object} config.details - Additional details.
|
||||
*/
|
||||
warn(config) {
|
||||
this.logger.warn(config.message, {
|
||||
service: config.service,
|
||||
method: config.method,
|
||||
details: config.details,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error message.
|
||||
* @param {Object} config - The configuration object.
|
||||
* @param {string} config.message - The message to log.
|
||||
* @param {string} config.service - The service name.
|
||||
* @param {string} config.method - The method name.
|
||||
* @param {Object} config.details - Additional details.
|
||||
*/
|
||||
error(config) {
|
||||
this.logger.error(config.message, {
|
||||
service: config.service,
|
||||
method: config.method,
|
||||
details: config.details,
|
||||
stack: config.stack,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const logger = new Logger();
|
||||
export { Logger };
|
||||
|
||||
export default logger;
|
||||
Reference in new issue
Block a user