From 992bf099fa0d66d7731c76b1b73c10df1fa300b0 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Thu, 26 Sep 2024 13:41:50 +0800 Subject: [PATCH] Add app settings model, routes, db ops, service, use settings for issueToken --- Server/configs/db.js | 2 +- Server/controllers/authController.js | 24 ++++--- Server/controllers/checkController.js | 8 +-- Server/controllers/settingsController.js | 20 ++++++ Server/db/mongo/MongoDB.js | 21 ++++++- Server/db/mongo/modules/settingsModule.js | 17 +++++ Server/db/mongo/modules/userModule.js | 1 + Server/index.js | 16 +++-- Server/models/AppSettings.js | 76 +++++++++++++++++++++++ Server/routes/settingsRoute.js | 8 +++ Server/service/settingsService.js | 33 ++++++++++ Server/utils/messages.js | 3 + 12 files changed, 210 insertions(+), 19 deletions(-) create mode 100644 Server/controllers/settingsController.js create mode 100644 Server/db/mongo/modules/settingsModule.js create mode 100644 Server/models/AppSettings.js create mode 100644 Server/routes/settingsRoute.js create mode 100644 Server/service/settingsService.js diff --git a/Server/configs/db.js b/Server/configs/db.js index ba3c8b660..9dd768da3 100644 --- a/Server/configs/db.js +++ b/Server/configs/db.js @@ -1,4 +1,4 @@ -const PORT = process.env.PORT || 5000; +const PORT = 5000; const connectDbAndRunServer = async (app, db) => { try { diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index a58d5e7a9..5f3d3e4bd 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -25,10 +25,15 @@ const { getTokenFromHeaders } = require("../utils/utils"); * @param {Object} payload * @returns {String} */ -const issueToken = (payload) => { - //TODO Add proper expiration date - const tokenTTL = process.env.TOKEN_TTL ? process.env.TOKEN_TTL : "2h"; - return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: tokenTTL }); +const issueToken = async (payload, appSettings) => { + try { + const tokenTTL = appSettings.jwtTTL ? appSettings.jwtTTL : "2h"; + return jwt.sign(payload, appSettings.jwtSecret, { expiresIn: tokenTTL }); + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "issueToken") : null; + next(error); + } }; /** @@ -67,7 +72,8 @@ const registerController = async (req, res, next) => { delete userForToken.profileImage; delete userForToken.avatarImage; - const token = issueToken(userForToken); + const appSettings = await req.settingsService.getSettings(); + const token = issueToken(userForToken, appSettings); req.emailService .buildAndSendEmail( @@ -130,8 +136,8 @@ const loginController = async (req, res, next) => { delete userWithoutPassword.avatarImage; // Happy path, return token - const token = issueToken(userWithoutPassword); - + const appSettings = req.settingsService.getSettings(); + const token = await issueToken(userWithoutPassword, appSettings); // reset avatar image userWithoutPassword.avatarImage = user.avatarImage; @@ -425,7 +431,9 @@ const resetPasswordController = async (req, res, next) => { } try { const user = await req.db.resetPassword(req, res); - const token = issueToken(user._doc); + + const appSettings = await req.settingsService.getSettings(); + const token = issueToken(user._doc, appSettings); res.status(200).json({ success: true, msg: successMessages.AUTH_RESET_PASSWORD, diff --git a/Server/controllers/checkController.js b/Server/controllers/checkController.js index 591703f57..bf30d9017 100644 --- a/Server/controllers/checkController.js +++ b/Server/controllers/checkController.js @@ -34,7 +34,7 @@ const createCheck = async (req, res, next) => { .status(200) .json({ success: true, msg: successMessages.CHECK_CREATE, data: check }); } catch (error) { - error.service === undefined ? (error.serivce = SERVICE_NAME) : null; + error.service === undefined ? (error.service = SERVICE_NAME) : null; error.method === undefined ? (error.method = "createCheck") : null; next(error); } @@ -62,7 +62,7 @@ const getChecks = async (req, res, next) => { data: { checksCount, checks }, }); } catch (error) { - error.service === undefined ? (error.serivce = SERVICE_NAME) : null; + error.service === undefined ? (error.service = SERVICE_NAME) : null; error.method === undefined ? (error.method = "getChecks") : null; next(error); } @@ -88,7 +88,7 @@ const getTeamChecks = async (req, res, next) => { data: checkData, }); } catch (error) { - error.service === undefined ? (error.serivce = SERVICE_NAME) : null; + error.service === undefined ? (error.service = SERVICE_NAME) : null; error.method === undefined ? (error.method = "getTeamChecks") : null; next(error); } @@ -114,7 +114,7 @@ const deleteChecks = async (req, res, next) => { data: { deletedCount }, }); } catch (error) { - error.service === undefined ? (error.serivce = SERVICE_NAME) : null; + error.service === undefined ? (error.service = SERVICE_NAME) : null; error.method === undefined ? (error.method = "deleteChecks") : null; next(error); } diff --git a/Server/controllers/settingsController.js b/Server/controllers/settingsController.js new file mode 100644 index 000000000..f4b5ae0f3 --- /dev/null +++ b/Server/controllers/settingsController.js @@ -0,0 +1,20 @@ +const { successMessages } = require("../utils/messages"); +const SERVICE_NAME = "SettingsController"; + +const getAppSettings = async (req, res, next) => { + try { + const settings = await req.db.getAppSettings(); + return res.status(200).json({ + success: true, + msg: successMessages.GET_APP_SETTINGS, + data: settings, + }); + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "getAppSettings") : null; + } +}; + +module.exports = { + getAppSettings, +}; diff --git a/Server/db/mongo/MongoDB.js b/Server/db/mongo/MongoDB.js index 8bb51b898..a8f527391 100644 --- a/Server/db/mongo/MongoDB.js +++ b/Server/db/mongo/MongoDB.js @@ -1,5 +1,6 @@ const mongoose = require("mongoose"); const UserModel = require("../../models/User"); +const AppSettings = require("../../models/AppSettings"); //**************************************** // DB Connection @@ -7,7 +8,16 @@ const UserModel = require("../../models/User"); const connect = async () => { try { - await mongoose.connect(process.env.DB_CONNECTION_STRING); + const connectionString = + process.env.DB_CONNECTION_STRING || "mongodb://localhost:27017/uptime_db"; + await mongoose.connect(connectionString); + // If there are no AppSettings, create one + let appSettings = await AppSettings.find(); + if (appSettings.length === 0) { + appSettings = new AppSettings({}); + await appSettings.save(); + } + console.log("Connected to MongoDB"); } catch (error) { console.error("Failed to connect to MongoDB"); @@ -115,12 +125,20 @@ const { deleteMaintenanceWindowByUserId, } = require("./modules/maintenaceWindowModule"); +//**************************************** +// Notifications +//**************************************** const { createNotification, getNotificationsByMonitorId, deleteNotificationsByMonitorId, } = require("./modules/notificationModule"); +//**************************************** +// AppSettings +//**************************************** +const { getAppSettings } = require("./modules/settingsModule"); + module.exports = { connect, insertUser, @@ -168,4 +186,5 @@ module.exports = { createNotification, getNotificationsByMonitorId, deleteNotificationsByMonitorId, + getAppSettings, }; diff --git a/Server/db/mongo/modules/settingsModule.js b/Server/db/mongo/modules/settingsModule.js new file mode 100644 index 000000000..defc3e870 --- /dev/null +++ b/Server/db/mongo/modules/settingsModule.js @@ -0,0 +1,17 @@ +const AppSettings = require("../../../models/AppSettings"); +const SERVICE_NAME = "SettingsModule"; + +const getAppSettings = async () => { + try { + const settings = AppSettings.findOne(); + return settings; + } catch (error) { + error.service = SERVICE_NAME; + error.method = "getSettings"; + throw error; + } +}; + +module.exports = { + getAppSettings, +}; diff --git a/Server/db/mongo/modules/userModule.js b/Server/db/mongo/modules/userModule.js index 436ff1be5..bfdd54a4c 100644 --- a/Server/db/mongo/modules/userModule.js +++ b/Server/db/mongo/modules/userModule.js @@ -35,6 +35,7 @@ const insertUser = async (userData, imageFile) => { email: userData.email, }); userData.teamId = team._id; + userData.checkTTL = 60 * 60 * 24 * 30; await team.save(); } diff --git a/Server/index.js b/Server/index.js index 05c9d3059..c7974bb60 100644 --- a/Server/index.js +++ b/Server/index.js @@ -5,7 +5,6 @@ const swaggerUi = require("swagger-ui-express"); const express = require("express"); const helmet = require("helmet"); const cors = require("cors"); -require("dotenv").config(); const logger = require("./utils/logger"); const { verifyJWT } = require("./middleware/verifyJWT"); const { handleErrors } = require("./middleware/handleErrors"); @@ -15,6 +14,7 @@ const inviteRouter = require("./routes/inviteRoute"); const monitorRouter = require("./routes/monitorRoute"); const checkRouter = require("./routes/checkRoute"); const maintenanceWindowRouter = require("./routes/maintenanceWindowRoute"); +const settingsRouter = require("./routes/settingsRoute"); const { connectDbAndRunServer } = require("./configs/db"); const queueRouter = require("./routes/queueRoute"); @@ -22,6 +22,7 @@ const JobQueue = require("./service/jobQueue"); const NetworkService = require("./service/networkService"); const EmailService = require("./service/emailService"); const PageSpeedService = require("./service/pageSpeedService"); +const SettingsService = require("./service/settingsService"); const SERVICE_NAME = "Server"; let cleaningUp = false; @@ -47,9 +48,11 @@ const startApp = async () => { FakedDB: () => require("./db/FakeDb"), }; - const db = DB_TYPE[process.env.DB_TYPE] - ? DB_TYPE[process.env.DB_TYPE]() - : require("./db/FakeDb"); + // 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(); @@ -60,7 +63,6 @@ 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 @@ -72,6 +74,7 @@ const startApp = async () => { req.jobQueue = jobQueue; req.emailService = emailService; req.pageSpeedService = pageSpeedService; + req.settingsService = settingsService; next(); }); @@ -80,6 +83,7 @@ const startApp = async () => { //routes app.use("/api/v1/auth", authRouter); + app.use("/api/v1/settings", verifyJWT, settingsRouter); app.use("/api/v1/invite", inviteRouter); app.use("/api/v1/monitors", verifyJWT, monitorRouter); app.use("/api/v1/checks", verifyJWT, checkRouter); @@ -126,6 +130,8 @@ const startApp = async () => { const networkService = new NetworkService(db, emailService); const jobQueue = await JobQueue.createJobQueue(db, networkService); const pageSpeedService = new PageSpeedService(); + const settingsService = new SettingsService(); + settingsService.loadSettings(); const cleanup = async () => { if (cleaningUp) { diff --git a/Server/models/AppSettings.js b/Server/models/AppSettings.js new file mode 100644 index 000000000..3a146e46f --- /dev/null +++ b/Server/models/AppSettings.js @@ -0,0 +1,76 @@ +const mongoose = require("mongoose"); + +const AppSettingsSchema = mongoose.Schema( + { + apiBaseUrl: { + type: String, + required: true, + default: "http://localhost:5000/api/v1", + }, + logLevel: { + type: String, + default: "debug", + enum: ["debug", "none", "error", "warn"], + }, + clientHost: { + type: String, + required: true, + default: "http://localhost:5173", + }, + jwtSecret: { + type: String, + required: true, + default: "my_secret", + }, + dbType: { + type: String, + required: true, + default: "MongoDB", + }, + dbConnectionString: { + type: String, + required: true, + default: "mongodb://localhost:27017/uptime_db", + }, + redisHost: { + type: String, + required: true, + default: "127.0.0.1", + }, + redisPort: { + type: Number, + default: "6379", + }, + jwtTTL: { + type: String, + required: true, + default: "99d", + }, + pagespeedApiKey: { + type: String, + }, + systemEmailHost: { + type: String, + }, + systemEmailPort: { + type: Number, + }, + systemEmailAddress: { + type: String, + }, + systemEmailAddress: { + type: String, + }, + singleton: { + type: Boolean, + required: true, + unique: true, + default: true, + }, + }, + { + timestamps: true, + } +); + +module.exports = mongoose.model("AppSettings", AppSettingsSchema); diff --git a/Server/routes/settingsRoute.js b/Server/routes/settingsRoute.js new file mode 100644 index 000000000..4c0acf1b8 --- /dev/null +++ b/Server/routes/settingsRoute.js @@ -0,0 +1,8 @@ +const router = require("express").Router(); +const settingsController = require("../controllers/settingsController"); +const { isAllowed } = require("../middleware/isAllowed"); +const Monitor = require("../models/Monitor"); + +router.get("/", isAllowed(["superadmin"]), settingsController.getAppSettings); + +module.exports = router; diff --git a/Server/service/settingsService.js b/Server/service/settingsService.js new file mode 100644 index 000000000..e8abe747f --- /dev/null +++ b/Server/service/settingsService.js @@ -0,0 +1,33 @@ +const AppSettings = require("../models/AppSettings"); +const SERVICE_NAME = "SettingsService"; + +class SettingsService { + constructor() { + this.settings = null; + } + + async loadSettings() { + if (!this.settings) { + try { + this.settings = await AppSettings.findOne(); + if (!this.settings) { + throw new Error("Settings not found"); + } + return this.settings; + } catch (error) { + error.service === undefined ? (error.service = SERVICE_NAME) : null; + error.method === undefined ? (error.method = "loadSettings") : null; + throw error; + } + } + } + + getSettings() { + if (!this.settings) { + throw new Error("Settings have not been loaded"); + } + return this.settings; + } +} + +module.exports = SettingsService; diff --git a/Server/utils/messages.js b/Server/utils/messages.js index 04da91491..14e1af6db 100644 --- a/Server/utils/messages.js +++ b/Server/utils/messages.js @@ -98,6 +98,9 @@ const successMessages = { //Ping Operations PING_SUCCESS: "Success", + + // App Settings + GET_APP_SETTINGS: "Got app settings successfully", }; module.exports = {