add update settings method, remove all env var references possible

This commit is contained in:
Alex Holliday
2024-09-26 14:37:11 +08:00
parent 992bf099fa
commit 11b97ab3b4
17 changed files with 149 additions and 36 deletions

View File

@@ -25,7 +25,7 @@ const { getTokenFromHeaders } = require("../utils/utils");
* @param {Object} payload
* @returns {String}
*/
const issueToken = async (payload, appSettings) => {
const issueToken = (payload, appSettings) => {
try {
const tokenTTL = appSettings.jwtTTL ? appSettings.jwtTTL : "2h";
return jwt.sign(payload, appSettings.jwtSecret, { expiresIn: tokenTTL });
@@ -74,7 +74,6 @@ const registerController = async (req, res, next) => {
const appSettings = await req.settingsService.getSettings();
const token = issueToken(userForToken, appSettings);
req.emailService
.buildAndSendEmail(
"welcomeEmailTemplate",
@@ -137,7 +136,7 @@ const loginController = async (req, res, next) => {
// Happy path, return token
const appSettings = req.settingsService.getSettings();
const token = await issueToken(userWithoutPassword, appSettings);
const token = issueToken(userWithoutPassword, appSettings);
// reset avatar image
userWithoutPassword.avatarImage = user.avatarImage;
@@ -186,7 +185,8 @@ const userEditController = async (req, res, next) => {
// Get token from headers
const token = getTokenFromHeaders(req.headers);
// Get email from token
const { email } = jwt.verify(token, process.env.JWT_SECRET);
const { jwtSecret } = req.settingsService.getSettings();
const { email } = jwt.verify(token, jwtSecret);
// Add user email to body for DB operation
req.body.email = email;
// Get user
@@ -236,12 +236,13 @@ const inviteController = async (req, res, next) => {
}
const inviteToken = await req.db.requestInviteToken(req, res);
const { clientHost } = req.settingsService.getSettings();
req.emailService
.buildAndSendEmail(
"employeeActivationTemplate",
{
name: firstname,
link: `${process.env.CLIENT_HOST}/register/${inviteToken.token}`,
link: `${clientHost}/register/${inviteToken.token}`,
},
req.body.email,
"Welcome to Uptime Monitor"
@@ -345,7 +346,8 @@ const recoveryRequestController = async (req, res, next) => {
const recoveryToken = await req.db.requestRecoveryToken(req, res);
const name = user.firstName;
const email = req.body.email;
const url = `${process.env.CLIENT_HOST}/set-new-password/${recoveryToken.token}`;
const { clientHost } = req.settingsService.getSettings();
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
const msgId = await req.emailService.buildAndSendEmail(
"passwordResetTemplate",

View File

@@ -163,7 +163,8 @@ const updateChecksTTL = async (req, res, next) => {
try {
// Get user's teamId
const token = getTokenFromHeaders(req.headers);
const { teamId } = jwt.verify(token, process.env.JWT_SECRET);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const ttl = parseInt(req.body.ttl, 10) * SECONDS_PER_DAY;
await req.db.updateChecksTTL(teamId, ttl);
return res.status(200).json({

View File

@@ -39,12 +39,13 @@ const inviteController = async (req, res, next) => {
}
const inviteToken = await req.db.requestInviteToken({ ...req.body });
const { clientHost } = req.settingsService.getSettings();
req.emailService
.buildAndSendEmail(
"employeeActivationTemplate",
{
name: firstname,
link: `${process.env.CLIENT_HOST}/register/${inviteToken.token}`,
link: `${clientHost}/register/${inviteToken.token}`,
},
req.body.email,
"Welcome to Uptime Monitor"

View File

@@ -345,7 +345,8 @@ const deleteMonitor = async (req, res, next) => {
const deleteAllMonitors = async (req, res) => {
try {
const token = getTokenFromHeaders(req.headers);
const { teamId } = jwt.verify(token, process.env.JWT_SECRET);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const { monitors, deletedCount } = await req.db.deleteAllMonitors(teamId);
await monitors.forEach(async (monitor) => {
await req.jobQueue.deleteJob(monitor);
@@ -460,7 +461,9 @@ const pauseMonitor = async (req, res, next) => {
const addDemoMonitors = async (req, res, next) => {
try {
const token = getTokenFromHeaders(req.headers);
const { _id, teamId } = jwt.verify(token, process.env.JWT_SECRET);
const { jwtSecret } = req.settingsService.getSettings();
const { _id, teamId } = jwt.verify(token, jwtSecret);
const demoMonitors = await req.db.addDemoMonitors(_id, teamId);
await demoMonitors.forEach(async (monitor) => {
await req.jobQueue.addJob(monitor._id, monitor);

View File

@@ -1,5 +1,6 @@
const { successMessages } = require("../utils/messages");
const SERVICE_NAME = "SettingsController";
const { updateAppSettingsBodyValidation } = require("../validation/joi");
const getAppSettings = async (req, res, next) => {
try {
@@ -12,9 +13,37 @@ const getAppSettings = async (req, res, next) => {
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "getAppSettings") : null;
next(error);
}
};
const updateAppSettings = async (req, res, next) => {
try {
await updateAppSettingsBodyValidation.validateAsync(req.body);
} catch (error) {
error.status = 422;
error.service = SERVICE_NAME;
error.message =
error.details?.[0]?.message || error.message || "Validation Error";
next(error);
return;
}
try {
const settings = await req.db.updateAppSettings(req.body);
return res.status(200).json({
success: true,
msg: successMessages.UPDATE_APP_SETTINGS,
data: settings,
});
} catch (error) {
error.service === undefined ? (error.service = SERVICE_NAME) : null;
error.method === undefined ? (error.method = "updateAppSettings") : null;
next(error);
}
};
module.exports = {
getAppSettings,
updateAppSettings,
};

View File

@@ -137,7 +137,10 @@ const {
//****************************************
// AppSettings
//****************************************
const { getAppSettings } = require("./modules/settingsModule");
const {
getAppSettings,
updateAppSettings,
} = require("./modules/settingsModule");
module.exports = {
connect,
@@ -187,4 +190,5 @@ module.exports = {
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
getAppSettings,
updateAppSettings,
};

View File

@@ -12,6 +12,22 @@ const getAppSettings = async () => {
}
};
const updateAppSettings = async (newSettings) => {
try {
const settings = await AppSettings.findOneAndUpdate(
{},
{ $set: newSettings },
{ new: true }
);
return settings;
} catch (error) {
error.service = SERVICE_NAME;
error.method = "updateAppSettings";
throw error;
}
};
module.exports = {
getAppSettings,
updateAppSettings,
};

View File

@@ -126,12 +126,17 @@ const startApp = async () => {
// Create services
await connectDbAndRunServer(app, db);
const emailService = new EmailService();
const networkService = new NetworkService(db, emailService);
const jobQueue = await JobQueue.createJobQueue(db, networkService);
const pageSpeedService = new PageSpeedService();
const settingsService = new SettingsService();
settingsService.loadSettings();
await settingsService.loadSettings();
const emailService = new EmailService(settingsService);
const networkService = new NetworkService(db, emailService);
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
settingsService
);
const pageSpeedService = new PageSpeedService();
const cleanup = async () => {
if (cleaningUp) {

View File

@@ -27,7 +27,8 @@ const isAllowed = (allowedRoles) => {
// Parse the token
try {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
var decoded = jwt.verify(parsedToken, process.env.JWT_SECRET);
const { jwtSecret } = req.settingsService.getSettings();
var decoded = jwt.verify(parsedToken, jwtSecret);
const userRoles = decoded.role;
// Check if the user has the required role

View File

@@ -35,7 +35,9 @@ const verifyJWT = (req, res, next) => {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
// Verify the token's authenticity
jwt.verify(parsedToken, process.env.JWT_SECRET, (err, decoded) => {
const { jwtSecret } = req.settingsService.getSettings();
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
if (err) {
return res
.status(401)

View File

@@ -33,7 +33,9 @@ const verifySuperAdmin = (req, res, next) => {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
// verify admin role is present
jwt.verify(parsedToken, process.env.JWT_SECRET, (err, decoded) => {
const { jwtSecret } = req.settingsService.getSettings();
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
if (err) {
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
service: SERVICE_NAME,

View File

@@ -51,15 +51,19 @@ const AppSettingsSchema = mongoose.Schema(
},
systemEmailHost: {
type: String,
default: "smtp.gmail.com",
},
systemEmailPort: {
type: Number,
default: 465,
},
systemEmailAddress: {
type: String,
default: "",
},
systemEmailAddress: {
systemEmailPassword: {
type: String,
default: "",
},
singleton: {
type: Boolean,

View File

@@ -4,5 +4,10 @@ const { isAllowed } = require("../middleware/isAllowed");
const Monitor = require("../models/Monitor");
router.get("/", isAllowed(["superadmin"]), settingsController.getAppSettings);
router.put(
"/",
isAllowed(["superadmin"]),
settingsController.updateAppSettings
);
module.exports = router;

View File

@@ -13,7 +13,8 @@ class EmailService {
/**
* Constructs an instance of the EmailService, initializing template loaders and the email transporter.
*/
constructor() {
constructor(settingsService) {
this.settingsService = settingsService;
/**
* Loads an email template from the filesystem.
*
@@ -54,15 +55,26 @@ class EmailService {
* The email transporter used to send emails.
* @type {Object}
*/
this.transporter = nodemailer.createTransport({
host: process.env.SYSTEM_EMAIL_HOST,
port: process.env.SYSTEM_EMAIL_PORT,
secure: true, // Use `true` for port 465, `false` for all other ports
const {
systemEmailHost,
systemEmailPort,
systemEmailAddress,
systemEmailPassword,
} = this.settingsService.getSettings();
const emailConfig = {
host: systemEmailHost,
port: systemEmailPort,
secure: true,
auth: {
user: process.env.SYSTEM_EMAIL_ADDRESS,
pass: process.env.SYSTEM_EMAIL_PASSWORD,
user: systemEmailAddress,
pass: systemEmailPassword,
},
});
};
console.log(emailConfig);
this.transporter = nodemailer.createTransport(emailConfig);
}
/**

View File

@@ -1,9 +1,6 @@
const { Queue, Worker, Job } = require("bullmq");
const QUEUE_NAME = "monitors";
const connection = {
host: process.env.REDIS_HOST || "127.0.0.1",
port: process.env.REDIS_PORT || 6379,
};
const JOBS_PER_WORKER = 5;
const logger = require("../utils/logger");
const { errorMessages, successMessages } = require("../utils/messages");
@@ -15,13 +12,20 @@ class JobQueue {
* @constructor
* @throws {Error}
*/
constructor(networkService) {
constructor(settingsService) {
const { redisHost, redisPort } = settingsService.getSettings();
const connection = {
host: redisHost || "127.0.0.1",
port: redisPort || 6379,
};
this.connection = connection;
this.queue = new Queue(QUEUE_NAME, {
connection,
});
this.workers = [];
this.db = null;
this.networkService = null;
this.settingsService = settingsService;
}
/**
@@ -31,8 +35,8 @@ class JobQueue {
* @returns {Promise<JobQueue>} - Returns a new JobQueue
*
*/
static async createJobQueue(db, networkService) {
const queue = new JobQueue();
static async createJobQueue(db, networkService, settingsService) {
const queue = new JobQueue(settingsService);
try {
queue.db = db;
queue.networkService = networkService;
@@ -99,7 +103,7 @@ class JobQueue {
}
},
{
connection,
connection: this.connection,
}
);
return worker;

View File

@@ -101,6 +101,7 @@ const successMessages = {
// App Settings
GET_APP_SETTINGS: "Got app settings successfully",
UPDATE_APP_SETTINGS: "Updated app settings successfully",
};
module.exports = {

View File

@@ -382,6 +382,26 @@ const getMaintenanceWindowsByMonitorIdParamValidation = joi.object({
monitorId: joi.string().required(),
});
//****************************************
// SettingsValidation
//****************************************
const updateAppSettingsBodyValidation = joi.object({
apiBaseUrl: joi.string().allow(""),
logLevel: joi.string().valid("debug", "none", "error", "warn").allow(""),
clientHost: joi.string().allow(""),
jwtSecret: joi.string().allow(""),
dbType: joi.string().allow(""),
dbConnectionString: joi.string().allow(""),
redisHost: joi.string().allow(""),
redisPort: joi.number().allow(null, ""),
jwtTTL: joi.string().allow(""),
pagespeedApiKey: joi.string().allow(""),
systemEmailHost: joi.string().allow(""),
systemEmailPort: joi.number().allow(""),
systemEmailAddress: joi.string().allow(""),
systemEmailPassword: joi.string().allow(""),
});
module.exports = {
roleValidatior,
loginValidation,
@@ -432,4 +452,5 @@ module.exports = {
createMaintenanceWindowBodyValidation,
getMaintenanceWindowsByUserIdParamValidation,
getMaintenanceWindowsByMonitorIdParamValidation,
updateAppSettingsBodyValidation,
};