From def8ba4b9df81f6dd6854491e5b0ff14a1e5d40a Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 May 2024 13:36:04 -0700 Subject: [PATCH 01/15] Added monitor router, controller, and model --- Server/controllers/monitorController.js | 19 ++++++++++ Server/index.js | 46 ++++++++++++++----------- Server/models/monitor.js | 37 ++++++++++++++++++++ Server/routes/monitorRoute.js | 8 +++++ Server/validation/joi.js | 12 ++++--- 5 files changed, 96 insertions(+), 26 deletions(-) create mode 100644 Server/controllers/monitorController.js create mode 100644 Server/models/monitor.js create mode 100644 Server/routes/monitorRoute.js diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js new file mode 100644 index 000000000..1044d3f48 --- /dev/null +++ b/Server/controllers/monitorController.js @@ -0,0 +1,19 @@ +const { getMonitorsByIdValidation } = require("../validation/joi"); + +const getAllMonitors = async (req, res) => { + res.json({ message: "Hello from Monitor Controller" }); +}; + +const getMonitorById = async (req, res) => { + const { error } = getMonitorsByIdValidation.validate(req.params); + if (error) { + return res + .status(400) + .json({ success: false, msg: error.details[0].message }); + } + + const monitorId = req.params.monitorId; + return res.json({ success: true, msg: "Monitor found", data: { monitorId } }); +}; + +module.exports = { getAllMonitors, getMonitorById }; diff --git a/Server/index.js b/Server/index.js index 173c2c78d..3913d1a58 100644 --- a/Server/index.js +++ b/Server/index.js @@ -1,28 +1,32 @@ -const express = require('express') -const helmet = require('helmet') -const cors = require('cors') -const authRouter = require('./routes/authRoute') -const { connectDbAndRunServer } = require('./configs/db') -require('dotenv').config() +const express = require("express"); +const helmet = require("helmet"); +const cors = require("cors"); +const authRouter = require("./routes/authRoute"); +const monitorRouter = require("./routes/monitorRoute"); +const { connectDbAndRunServer } = require("./configs/db"); +require("dotenv").config(); // const { sendEmail } = require('./utils/sendEmail') /** * NOTES - * Email Service will be added + * Email Service will be added * Logger Service will be added (Winston or similar) */ -const app = express() +const app = express(); // middlewares -app.use(cors( - //We will add configuration later -)) -app.use(express.json()) -app.use(helmet()) +app.use( + cors() + //We will add configuration later +); +app.use(express.json()); +app.use(helmet()); //routes -app.use('/api/v1/auth', authRouter); +app.use("/api/v1/auth", authRouter); + +app.use("/api/v1/monitors", monitorRouter); // Testing email service // app.use('/sendEmail', async (req, res) => { @@ -31,12 +35,12 @@ app.use('/api/v1/auth', authRouter); // }) //health check -app.use('/api/v1/healthy', (req, res) => { - try { - return res.status(200).json({message:"Healthy"}) - } catch (error) { - return res.status(500).json({message:error.message}) - } -}) +app.use("/api/v1/healthy", (req, res) => { + try { + return res.status(200).json({ message: "Healthy" }); + } catch (error) { + return res.status(500).json({ message: error.message }); + } +}); connectDbAndRunServer(app); diff --git a/Server/models/monitor.js b/Server/models/monitor.js new file mode 100644 index 000000000..513d4e2be --- /dev/null +++ b/Server/models/monitor.js @@ -0,0 +1,37 @@ +const mongoose = require("mongoose"); + +const MonitorSchema = mongoose.Schema({ + userId: { + type: String, + }, + name: { + type: String, + required: true, + }, + description: { + type: String, + }, + url: { + type: String, + required: true, + }, + isActive: { + type: Boolean, + default: true, + }, + interval: { + // in milliseconds + type: Number, + default: 60000, + }, + updated_at: { + type: Date, + default: Date.now, + }, + created_at: { + type: Date, + default: Date.now, + }, +}); + +module.exports = mongoose.model("Monitor", MonitorSchema); diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js new file mode 100644 index 000000000..724ee79b6 --- /dev/null +++ b/Server/routes/monitorRoute.js @@ -0,0 +1,8 @@ +const router = require("express").Router(); +const monitorController = require("../controllers/monitorController"); +const { getMonitorsByIdValidation } = require("../validation/joi"); + +router.get("/", monitorController.getAllMonitors); +router.get("/:monitorId", monitorController.getMonitorById); + +module.exports = router; diff --git a/Server/validation/joi.js b/Server/validation/joi.js index d28f9b7da..c5b2b58d1 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -1,10 +1,12 @@ -const joi = require('joi') - +const joi = require("joi"); const authValidation = joi.object({ - email: joi.string().email().required(), - password: joi.string().min(8).required(), + email: joi.string().email().required(), + password: joi.string().min(8).required(), }); +const getMonitorsByIdValidation = joi.object({ + monitorId: joi.string().required(), +}); -module.exports = {authValidation} \ No newline at end of file +module.exports = { authValidation, getMonitorsByIdValidation }; From 2e2d91937e533b1e0026324dc019018032227f9f Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 May 2024 13:59:33 -0700 Subject: [PATCH 02/15] Added fake DB for monitor route testing --- Server/controllers/monitorController.js | 48 +++++++++++++++++++++--- Server/db/FakeDb.js | 46 +++++++++++++++++++++++ Server/models/{monitor.js => Monitor.js} | 0 Server/routes/monitorRoute.js | 2 +- Server/validation/joi.js | 11 +++++- 5 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 Server/db/FakeDb.js rename Server/models/{monitor.js => Monitor.js} (100%) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index 1044d3f48..f7a85ce4c 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -1,9 +1,20 @@ -const { getMonitorsByIdValidation } = require("../validation/joi"); +const { + getMonitorsByIdValidation, + getMonitorsByUserIdValidation, +} = require("../validation/joi"); +const db = require("../db/FakeDb"); +// Gets all monitors const getAllMonitors = async (req, res) => { - res.json({ message: "Hello from Monitor Controller" }); + try { + const monitors = await db.getAllMonitors(); + return res.json({ success: true, msg: "Monitors found", data: monitors }); + } catch (error) { + return res.status(500).json({ success: false, msg: error.message }); + } }; +// Get a monitor by ID const getMonitorById = async (req, res) => { const { error } = getMonitorsByIdValidation.validate(req.params); if (error) { @@ -12,8 +23,35 @@ const getMonitorById = async (req, res) => { .json({ success: false, msg: error.details[0].message }); } - const monitorId = req.params.monitorId; - return res.json({ success: true, msg: "Monitor found", data: { monitorId } }); + try { + const monitorId = req.params.monitorId; + const monitor = await db.getMonitorById(monitorId); + return res.json({ success: true, msg: "Monitor found", data: monitor }); + } catch (error) { + return res.status(500).json({ success: false, msg: error.message }); + } }; -module.exports = { getAllMonitors, getMonitorById }; +// Gets a monitor by user ID +const getMonitorsByUserId = async (req, res) => { + const { error } = getMonitorsByUserIdValidation.validate(req.params); + if (error) { + return res + .status(400) + .json({ success: false, msg: error.details[0].message }); + } + + try { + const userId = req.params.userId; + const monitors = await db.getMonitorsByUserId(userId); + return res.json({ + success: true, + msg: `Monitors for user ${userId} found`, + data: monitors, + }); + } catch (error) { + return res.status(500).json({ success: false, msg: error.message }); + } +}; + +module.exports = { getAllMonitors, getMonitorById, getMonitorsByUserId }; diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js new file mode 100644 index 000000000..b604a9294 --- /dev/null +++ b/Server/db/FakeDb.js @@ -0,0 +1,46 @@ +const Monitor = require("../models/Monitor"); + +const FAKE_MONITOR_DATA = []; + +for (let i = 0; i < 10; i++) { + FAKE_MONITOR_DATA.push( + new Monitor({ + userId: i % 2 === 0 ? 1 : 2, + name: `Monitor ${i}`, + description: `Description for Monitor ${i}`, + url: `https://monitor${i}.com`, + isActive: true, + interval: 60000, + updated_at: new Date(), + created_at: new Date(), + }) + ); +} + +const getAllMonitors = async () => { + return FAKE_MONITOR_DATA; +}; + +const getMonitorById = async (monitorId) => { + const idx = FAKE_MONITOR_DATA.findIndex((monitor) => { + return monitor.id === monitorId; + }); + if (idx === -1) { + throw new Error(`Monitor with id ${monitorId} not found`); + } + return FAKE_MONITOR_DATA[idx]; +}; + +const getMonitorsByUserId = async (userId) => { + const userMonitors = FAKE_MONITOR_DATA.filter((monitor) => { + return monitor.userId === userId; + }); + + if (userMonitors.length === 0) { + throw new Error(`Monitors for user ${userId} not found`); + } + + return userMonitors; +}; + +module.exports = { getAllMonitors, getMonitorById, getMonitorsByUserId }; diff --git a/Server/models/monitor.js b/Server/models/Monitor.js similarity index 100% rename from Server/models/monitor.js rename to Server/models/Monitor.js diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index 724ee79b6..7c6767364 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -1,8 +1,8 @@ const router = require("express").Router(); const monitorController = require("../controllers/monitorController"); -const { getMonitorsByIdValidation } = require("../validation/joi"); router.get("/", monitorController.getAllMonitors); router.get("/:monitorId", monitorController.getMonitorById); +router.get("/user/:userId", monitorController.getMonitorsByUserId); module.exports = router; diff --git a/Server/validation/joi.js b/Server/validation/joi.js index c5b2b58d1..2fb59a377 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -1,4 +1,5 @@ const joi = require("joi"); +const user = require("../models/user"); const authValidation = joi.object({ email: joi.string().email().required(), @@ -9,4 +10,12 @@ const getMonitorsByIdValidation = joi.object({ monitorId: joi.string().required(), }); -module.exports = { authValidation, getMonitorsByIdValidation }; +const getMonitorsByUserIdValidation = joi.object({ + userId: joi.string().required(), +}); + +module.exports = { + authValidation, + getMonitorsByIdValidation, + getMonitorsByUserIdValidation, +}; From 49a11b5abbe57b4526242f235c02e3f2af18d60e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 May 2024 14:03:36 -0700 Subject: [PATCH 03/15] Added some comments RE fake database --- Server/controllers/monitorController.js | 2 ++ Server/db/FakeDb.js | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index f7a85ce4c..deaf2d8ba 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -2,6 +2,8 @@ const { getMonitorsByIdValidation, getMonitorsByUserIdValidation, } = require("../validation/joi"); + +// Fake database, if we have standardized DB methods we can swap out databases easily const db = require("../db/FakeDb"); // Gets all monitors diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js index b604a9294..8eed7779c 100644 --- a/Server/db/FakeDb.js +++ b/Server/db/FakeDb.js @@ -1,3 +1,9 @@ +// ************************** +// FakeDB is used to simulate the database +// If we standarize the DB methods we can swap out databases easily +// Too bad JS doesn't have interfaces, that would be nice. TypeScript anyone? +// ************************** + const Monitor = require("../models/Monitor"); const FAKE_MONITOR_DATA = []; From 7d6d2d341b1edfd313e6cf1c97cac8d0b9b68143 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 May 2024 14:04:13 -0700 Subject: [PATCH 04/15] Added one last comment --- Server/db/FakeDb.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js index 8eed7779c..d3b793f11 100644 --- a/Server/db/FakeDb.js +++ b/Server/db/FakeDb.js @@ -49,4 +49,5 @@ const getMonitorsByUserId = async (userId) => { return userMonitors; }; +// Conisder standardizing these methods so we can swap databases easily module.exports = { getAllMonitors, getMonitorById, getMonitorsByUserId }; From 8d6529d497c481935417d9dc187d03291d334f33 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Fri, 10 May 2024 14:34:04 -0700 Subject: [PATCH 05/15] Added some endpoint info to readme --- README.md | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/README.md b/README.md index 19a0670e1..4b5375c8f 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,103 @@ BlueWave uptime monitoring application 1. Change directory to the `Server` directory 2. Install all depencies by running `npm install` +--- + +#### Configuration + +Configure the server with the following environmental variables + +| ENV Varialbe Name | Required/Optional | Type | Description | +| -------------------- | ----------------- | ------ | --------------------------------- | +| DB_CONNECTION_STRING | Required | string | Specfies URL for MongoDB Database | + +--- + #### Starting the Development Server 1. run `npm run dev` to start the development server +--- + +#### Endpoints + +All endpoints return a response in this format: + +| Name | Type | Notes | +| ------- | --------- | ----------------------------- | +| success | `boolean` | Success or failure of request | +| msg | `string` | Message describing response | +| data | `Object` | Arbitrary Payload | + +Example: + +``` +{success: true, msg: "Successful Request", data: {test: testData}} +``` + +##### Data Types + +###### Monitor + +| Name | Type | Notes | +| ----------- | --------- | ---------------------------------------- | +| userId | `string` | Unique ID identifying monitor creator | +| name | `string` | Name of the monitor | +| description | `string` | Description of the monitor | +| url | `string` | Url the monitor will ping | +| isActive | `boolean` | Whether or not the monitor is active | +| interval | `integer` | Interval with which to ping monitor (ms) | +| updatedAt | `Date` | Last time the monitor was updated | +| CreatedAt | `Date` | When the monitor was updated | + +--- + +##### GET /api/v1/monitors + +###### Response + +| Status Code | Type | Description | +| ----------- | --------------------- | -------------------------------- | +| 200 | Response with Payload | Response with a list of monitors | + +###### Payload + +| Type | Notes | +| ---------------- | ----------------- | +| `Array[Monitor]` | Array of monitors | + +--- + +##### GET /api/v1/monitor/:id + +###### Response + +| Status Code | Type | Description | +| ----------- | --------------------- | ------------------------------ | +| 200 | Response with Payload | Response with a single monitor | + +###### Payload + +| Type | Notes | +| --------- | --------------------------------------------------- | +| `Monitor` | Single monitor with the id in the request parameter | + +--- + +##### GET /api/v1/monitors/user/:userId + +| Status Code | Type | Description | +| ----------- | --------------------- | -------------------------------- | +| 200 | Response with Payload | Response with a list of monitors | + +###### Payload + +| Type | Notes | +| ---------------- | ------------------------------------------------------------------ | +| `Array[Monitor]` | Array of monitors created by user with userId specified in request | + ## Contributors + From 8cc92e6c40d44f73a6bb92b5492707f5a34c5921 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 13 May 2024 14:21:27 -0700 Subject: [PATCH 06/15] Implemented MongoDB example db, updated documentation --- Server/configs/db.js | 29 ++++----- Server/controllers/monitorController.js | 9 +-- Server/db/FakeDb.js | 38 ++++++++++-- Server/db/MongoDB.js | 80 +++++++++++++++++++++++++ Server/index.js | 27 ++++++++- Server/routes/monitorRoute.js | 1 - 6 files changed, 154 insertions(+), 30 deletions(-) create mode 100644 Server/db/MongoDB.js diff --git a/Server/configs/db.js b/Server/configs/db.js index 174978ca3..ba3c8b660 100644 --- a/Server/configs/db.js +++ b/Server/configs/db.js @@ -1,18 +1,15 @@ -const mongoose = require('mongoose') +const PORT = process.env.PORT || 5000; -const PORT = process.env.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); + } +}; -const connectDbAndRunServer = async (app) => { - await mongoose.connect(process.env.DB_CONNECTION_STRING).then(() => { - console.log("DB connected") - // run server - app.listen(PORT, () => { - console.log(`server started on port:${PORT}`) - }) - }).catch((err) => { - console.log(err) - }) -} - - -module.exports = { connectDbAndRunServer } \ No newline at end of file +module.exports = { connectDbAndRunServer }; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index deaf2d8ba..dda18d8ff 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -3,13 +3,10 @@ const { getMonitorsByUserIdValidation, } = require("../validation/joi"); -// Fake database, if we have standardized DB methods we can swap out databases easily -const db = require("../db/FakeDb"); - // Gets all monitors const getAllMonitors = async (req, res) => { try { - const monitors = await db.getAllMonitors(); + const monitors = await req.db.getAllMonitors(); return res.json({ success: true, msg: "Monitors found", data: monitors }); } catch (error) { return res.status(500).json({ success: false, msg: error.message }); @@ -27,7 +24,7 @@ const getMonitorById = async (req, res) => { try { const monitorId = req.params.monitorId; - const monitor = await db.getMonitorById(monitorId); + const monitor = await req.db.getMonitorById(monitorId); return res.json({ success: true, msg: "Monitor found", data: monitor }); } catch (error) { return res.status(500).json({ success: false, msg: error.message }); @@ -45,7 +42,7 @@ const getMonitorsByUserId = async (req, res) => { try { const userId = req.params.userId; - const monitors = await db.getMonitorsByUserId(userId); + const monitors = await req.db.getMonitorsByUserId(userId); return res.json({ success: true, msg: `Monitors for user ${userId} found`, diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js index d3b793f11..6dfa89ac9 100644 --- a/Server/db/FakeDb.js +++ b/Server/db/FakeDb.js @@ -1,7 +1,23 @@ // ************************** -// FakeDB is used to simulate the database -// If we standarize the DB methods we can swap out databases easily -// Too bad JS doesn't have interfaces, that would be nice. TypeScript anyone? +// The idea here is to provide a layer of abstraction between the database and whoever is using it. +// Instead of directly calling mongoose methods, we can call the methods on the DB object. +// If this were Typescript or Java or Golang an interface would be implemented to ensure the methods are available. +// But we do the best we can with Javascript. +// +// If the methods are consistent all we have to do to swap out one DB for another is simply change the import. +// +// Example: +// We start with the fake DB: +// +// const db = require("../db/FakeDb"); +// const monitors = await db.getAllMonitors(); +// +// And when we want to swtich to a real DB, all we have to do is swap the import +// +// const db = require("../db/MongoDb"); +// const monitors = await db.getAllMonitors(); +// +// The rest of the code is the same, as all the `db` methods are standardized. // ************************** const Monitor = require("../models/Monitor"); @@ -23,6 +39,14 @@ for (let i = 0; i < 10; i++) { ); } +const connect = async () => { + try { + await console.log("Connected to FakeDB"); + } catch (error) { + console.error(error); + } +}; + const getAllMonitors = async () => { return FAKE_MONITOR_DATA; }; @@ -49,5 +73,9 @@ const getMonitorsByUserId = async (userId) => { return userMonitors; }; -// Conisder standardizing these methods so we can swap databases easily -module.exports = { getAllMonitors, getMonitorById, getMonitorsByUserId }; +module.exports = { + connect, + getAllMonitors, + getMonitorById, + getMonitorsByUserId, +}; diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js new file mode 100644 index 000000000..3c48a448d --- /dev/null +++ b/Server/db/MongoDB.js @@ -0,0 +1,80 @@ +const Monitor = require("../models/Monitor"); +const mongoose = require("mongoose"); + +const seedDatabase = async () => { + const FAKE_MONITOR_DATA = []; + + for (let i = 0; i < 10; i++) { + FAKE_MONITOR_DATA.push( + new Monitor({ + userId: i % 2 === 0 ? 1 : 2, + name: `Monitor ${i}`, + description: `Description for Monitor ${i}`, + url: `https://monitor${i}.com`, + isActive: true, + interval: 60000, + updated_at: new Date(), + created_at: new Date(), + }) + ); + } + + for (const monitor of FAKE_MONITOR_DATA) { + await monitor.save(); + } +}; + +const connect = async () => { + try { + await mongoose.connect(process.env.DB_CONNECTION_STRING); + console.log("Connected to MongoDB"); + + const monitors = await Monitor.find(); + if (monitors.length === 0) { + console.log("Seeding database with fake data"); + await seedDatabase(); + } else { + console.log("Database already seeded"); + } + } catch (error) { + console.error("Failed to connect to MongoDB"); + throw error; + } +}; + +// Gets all monitors +const getAllMonitors = async (req, res) => { + try { + const monitors = await Monitor.find(); + return monitors; + } catch (error) { + throw error; + } +}; + +// Get a monitor by ID +const getMonitorById = async (req, res) => { + try { + const monitor = await Monitor.findById(req.params.monitorId); + return monitor; + } catch (error) { + throw error; + } +}; + +// Gets a monitor by user ID +const getMonitorsByUserId = async (req, res) => { + try { + const monitors = await Monitor.find({ userId: req.params.userId }); + return monitors; + } catch (error) { + throw error; + } +}; + +module.exports = { + connect, + getAllMonitors, + getMonitorById, + getMonitorsByUserId, +}; diff --git a/Server/index.js b/Server/index.js index 3913d1a58..c8136c2d0 100644 --- a/Server/index.js +++ b/Server/index.js @@ -3,10 +3,23 @@ const helmet = require("helmet"); const cors = require("cors"); const authRouter = require("./routes/authRoute"); const monitorRouter = require("./routes/monitorRoute"); -const { connectDbAndRunServer } = require("./configs/db"); +const { connectDbAndRunServer, connectViaInterface } = require("./configs/db"); require("dotenv").config(); // const { sendEmail } = require('./utils/sendEmail') +// ************************** +// 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"); +// +// ************************** + /** * NOTES * Email Service will be added @@ -23,6 +36,16 @@ app.use( 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 +// ************************** +app.use((req, res, next) => { + req.db = db; + next(); +}); + //routes app.use("/api/v1/auth", authRouter); @@ -43,4 +66,4 @@ app.use("/api/v1/healthy", (req, res) => { } }); -connectDbAndRunServer(app); +connectDbAndRunServer(app, db); diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js index 7c6767364..e12982905 100644 --- a/Server/routes/monitorRoute.js +++ b/Server/routes/monitorRoute.js @@ -1,6 +1,5 @@ const router = require("express").Router(); const monitorController = require("../controllers/monitorController"); - router.get("/", monitorController.getAllMonitors); router.get("/:monitorId", monitorController.getMonitorById); router.get("/user/:userId", monitorController.getMonitorsByUserId); From 7957612a85a59229702e9e9652e7db895b30c1dc Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 13 May 2024 14:23:03 -0700 Subject: [PATCH 07/15] Removed unused method --- Server/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Server/index.js b/Server/index.js index c8136c2d0..ef8cdc982 100644 --- a/Server/index.js +++ b/Server/index.js @@ -3,7 +3,7 @@ const helmet = require("helmet"); const cors = require("cors"); const authRouter = require("./routes/authRoute"); const monitorRouter = require("./routes/monitorRoute"); -const { connectDbAndRunServer, connectViaInterface } = require("./configs/db"); +const { connectDbAndRunServer } = require("./configs/db"); require("dotenv").config(); // const { sendEmail } = require('./utils/sendEmail') From 1746cfaa13b3be90f5ee7b435c4a226db34edc9c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 13 May 2024 14:50:36 -0700 Subject: [PATCH 08/15] Fixed naming conventions to comply with standard --- .../Avatar/{avatar.css => index.css} | 0 .../Avatar/{Avatar.jsx => index.jsx} | 0 .../Button/{Button.css => index.css} | 0 .../Button/{Button.jsx => index.jsx} | 0 Client/src/Components/Label/BaseLabel.jsx | 54 ------ Client/src/Components/Label/ColoredLabel.jsx | 67 -------- Client/src/Components/Label/StautsLabel.jsx | 46 ----- .../Label/{BaseLabel.css => index.css} | 0 Client/src/Components/Label/index.jsx | 157 ++++++++++++++++++ .../Components/Link/{Link.jsx => index.jsx} | 0 .../Components/Link/{Link.css => link.css} | 0 .../{ProgressStepper.css => index.css} | 0 .../{ProgressStepper.jsx => index.jsx} | 0 .../Section/{Section.jsx => index.jsx} | 2 +- Client/src/Pages/Demo/Demo.jsx | 14 +- 15 files changed, 164 insertions(+), 176 deletions(-) rename Client/src/Components/Avatar/{avatar.css => index.css} (100%) rename Client/src/Components/Avatar/{Avatar.jsx => index.jsx} (100%) rename Client/src/Components/Button/{Button.css => index.css} (100%) rename Client/src/Components/Button/{Button.jsx => index.jsx} (100%) delete mode 100644 Client/src/Components/Label/BaseLabel.jsx delete mode 100644 Client/src/Components/Label/ColoredLabel.jsx delete mode 100644 Client/src/Components/Label/StautsLabel.jsx rename Client/src/Components/Label/{BaseLabel.css => index.css} (100%) create mode 100644 Client/src/Components/Label/index.jsx rename Client/src/Components/Link/{Link.jsx => index.jsx} (100%) rename Client/src/Components/Link/{Link.css => link.css} (100%) rename Client/src/Components/ProgressStepper/{ProgressStepper.css => index.css} (100%) rename Client/src/Components/ProgressStepper/{ProgressStepper.jsx => index.jsx} (100%) rename Client/src/Components/Section/{Section.jsx => index.jsx} (98%) diff --git a/Client/src/Components/Avatar/avatar.css b/Client/src/Components/Avatar/index.css similarity index 100% rename from Client/src/Components/Avatar/avatar.css rename to Client/src/Components/Avatar/index.css diff --git a/Client/src/Components/Avatar/Avatar.jsx b/Client/src/Components/Avatar/index.jsx similarity index 100% rename from Client/src/Components/Avatar/Avatar.jsx rename to Client/src/Components/Avatar/index.jsx diff --git a/Client/src/Components/Button/Button.css b/Client/src/Components/Button/index.css similarity index 100% rename from Client/src/Components/Button/Button.css rename to Client/src/Components/Button/index.css diff --git a/Client/src/Components/Button/Button.jsx b/Client/src/Components/Button/index.jsx similarity index 100% rename from Client/src/Components/Button/Button.jsx rename to Client/src/Components/Button/index.jsx diff --git a/Client/src/Components/Label/BaseLabel.jsx b/Client/src/Components/Label/BaseLabel.jsx deleted file mode 100644 index aa23aeab3..000000000 --- a/Client/src/Components/Label/BaseLabel.jsx +++ /dev/null @@ -1,54 +0,0 @@ -import PropTypes from "prop-types"; -import "./BaseLabel.css"; -import { useTheme } from "@mui/material"; - -/** - * @typedef {Object} Styles - * @param {string} [color] - The text color - * @param {string} [backgroundColor] - The background color - * @param {string} [borderColor] - The border color - */ - -/** - * @component - * @param {Object} props - * @param {string} props.label - The label of the label - * @param {Styles} props.styles - CSS Styles passed from parent component - * @param {React.ReactNode} children - Children passed from parent component - * @returns {JSX.Element} - */ - -const BaseLabel = ({ label, styles, children }) => { - const theme = useTheme(); - // Grab the default borderRadius from the theme to match button style - const { borderRadius } = theme.shape; - // Calculate padding for the label to mimic button. Appears to scale correctly, not 100% sure though. - const padding = theme.spacing(1 * 0.75, 2); - - return ( -
- {children} - {label} -
- ); -}; - -BaseLabel.propTypes = { - label: PropTypes.string.isRequired, - styles: PropTypes.shape({ - color: PropTypes.string, - backgroundColor: PropTypes.string, - }), - children: PropTypes.node, -}; - -export default BaseLabel; diff --git a/Client/src/Components/Label/ColoredLabel.jsx b/Client/src/Components/Label/ColoredLabel.jsx deleted file mode 100644 index 744652050..000000000 --- a/Client/src/Components/Label/ColoredLabel.jsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useTheme } from "@mui/material"; -import { PropTypes } from "prop-types"; -import BaseLabel from "./BaseLabel"; - -// Produces a lighter color based on a hex color and a percent -// lightenColor("#067647", 20) will produce a color 20% lighter than #067647 -const lightenColor = (color, percent) => { - let r = parseInt(color.substring(1, 3), 16); - let g = parseInt(color.substring(3, 5), 16); - let b = parseInt(color.substring(5, 7), 16); - - const amt = Math.round((255 * percent) / 100); - - r = r + amt <= 255 ? r + amt : 255; - g = g + amt <= 255 ? g + amt : 255; - b = b + amt <= 255 ? b + amt : 255; - - r = r.toString(16).padStart(2, "0"); - g = g.toString(16).padStart(2, "0"); - b = b.toString(16).padStart(2, "0"); - - return `#${r}${g}${b}`; -}; - -/** - * @component - * @param {Object} props - * @param {string} props.label - The label of the label - * @param {string} props.color - The color of the label, specified in #RRGGBB format - * @returns {JSX.Element} - * @example - * // Render a red label - * - */ - -const ColoredLabel = ({ label, color }) => { - const theme = useTheme(); - // If an invalid color is passed, default to the labelGray color - if ( - typeof color !== "string" || - !/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color) - ) { - color = theme.palette.labelGray.color; - } - - // Calculate lighter shades for border and bg - const borderColor = lightenColor(color, 20); - const bgColor = lightenColor(color, 75); - - return ( - - ); -}; - -ColoredLabel.propTypes = { - label: PropTypes.string.isRequired, - color: PropTypes.string.isRequired, -}; - -export default ColoredLabel; diff --git a/Client/src/Components/Label/StautsLabel.jsx b/Client/src/Components/Label/StautsLabel.jsx deleted file mode 100644 index 108ca0f2f..000000000 --- a/Client/src/Components/Label/StautsLabel.jsx +++ /dev/null @@ -1,46 +0,0 @@ -import PropTypes from "prop-types"; -import BaseLabel from "./BaseLabel"; -import { Box } from "@mui/material"; -import { useTheme } from "@mui/material"; - -/** - * @component - * @param {Object} props - * @param {'Seen' | 'Waiting' | 'New' | 'Active'} props.status - The status for the label - * @returns {JSX.Element} - * @example - * // Render an active label - * - */ - -const StatusLabel = ({ status }) => { - const theme = useTheme(); - - const colorLookup = { - Seen: theme.palette.labelGray.color, - Waiting: theme.palette.labelRed.color, - New: theme.palette.labelOrange.color, - Active: theme.palette.labelGreen.color, - }; - - // Look up the color for the status, default to labelGray if not found - const color = colorLookup[status] || theme.palette.labelGray.color; - - return ( - - - - ); -}; - -StatusLabel.propTypes = { - status: PropTypes.oneOf(["Seen", "Waiting", "New", "Active"]), -}; - -export default StatusLabel; diff --git a/Client/src/Components/Label/BaseLabel.css b/Client/src/Components/Label/index.css similarity index 100% rename from Client/src/Components/Label/BaseLabel.css rename to Client/src/Components/Label/index.css diff --git a/Client/src/Components/Label/index.jsx b/Client/src/Components/Label/index.jsx new file mode 100644 index 000000000..c47b53cb8 --- /dev/null +++ b/Client/src/Components/Label/index.jsx @@ -0,0 +1,157 @@ +import PropTypes from "prop-types"; +import { Box } from "@mui/material"; +import { useTheme } from "@mui/material"; +import "./index.css"; + +/** + * @typedef {Object} Styles + * @param {string} [color] - The text color + * @param {string} [backgroundColor] - The background color + * @param {string} [borderColor] - The border color + */ + +/** + * @component + * @param {Object} props + * @param {string} props.label - The label of the label + * @param {Styles} props.styles - CSS Styles passed from parent component + * @param {React.ReactNode} children - Children passed from parent component + * @returns {JSX.Element} + */ + +const BaseLabel = ({ label, styles, children }) => { + const theme = useTheme(); + // Grab the default borderRadius from the theme to match button style + const { borderRadius } = theme.shape; + // Calculate padding for the label to mimic button. Appears to scale correctly, not 100% sure though. + const padding = theme.spacing(1 * 0.75, 2); + + return ( +
+ {children} + {label} +
+ ); +}; + +BaseLabel.propTypes = { + label: PropTypes.string.isRequired, + styles: PropTypes.shape({ + color: PropTypes.string, + backgroundColor: PropTypes.string, + }), + children: PropTypes.node, +}; + +// Produces a lighter color based on a hex color and a percent +// lightenColor("#067647", 20) will produce a color 20% lighter than #067647 +const lightenColor = (color, percent) => { + let r = parseInt(color.substring(1, 3), 16); + let g = parseInt(color.substring(3, 5), 16); + let b = parseInt(color.substring(5, 7), 16); + + const amt = Math.round((255 * percent) / 100); + + r = r + amt <= 255 ? r + amt : 255; + g = g + amt <= 255 ? g + amt : 255; + b = b + amt <= 255 ? b + amt : 255; + + r = r.toString(16).padStart(2, "0"); + g = g.toString(16).padStart(2, "0"); + b = b.toString(16).padStart(2, "0"); + + return `#${r}${g}${b}`; +}; + +/** + * @component + * @param {Object} props + * @param {string} props.label - The label of the label + * @param {string} props.color - The color of the label, specified in #RRGGBB format + * @returns {JSX.Element} + * @example + * // Render a red label + * + */ + +const ColoredLabel = ({ label, color }) => { + const theme = useTheme(); + // If an invalid color is passed, default to the labelGray color + if ( + typeof color !== "string" || + !/^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(color) + ) { + color = theme.palette.labelGray.color; + } + + // Calculate lighter shades for border and bg + const borderColor = lightenColor(color, 20); + const bgColor = lightenColor(color, 75); + + return ( + + ); +}; + +ColoredLabel.propTypes = { + label: PropTypes.string.isRequired, + color: PropTypes.string.isRequired, +}; + +/** + * @component + * @param {Object} props + * @param {'Seen' | 'Waiting' | 'New' | 'Active'} props.status - The status for the label + * @returns {JSX.Element} + * @example + * // Render an active label + * + */ + +const StatusLabel = ({ status }) => { + const theme = useTheme(); + + const colorLookup = { + Seen: theme.palette.labelGray.color, + Waiting: theme.palette.labelRed.color, + New: theme.palette.labelOrange.color, + Active: theme.palette.labelGreen.color, + }; + + // Look up the color for the status, default to labelGray if not found + const color = colorLookup[status] || theme.palette.labelGray.color; + + return ( + + + + ); +}; + +StatusLabel.propTypes = { + status: PropTypes.oneOf(["Seen", "Waiting", "New", "Active"]), +}; + +export { ColoredLabel, StatusLabel }; diff --git a/Client/src/Components/Link/Link.jsx b/Client/src/Components/Link/index.jsx similarity index 100% rename from Client/src/Components/Link/Link.jsx rename to Client/src/Components/Link/index.jsx diff --git a/Client/src/Components/Link/Link.css b/Client/src/Components/Link/link.css similarity index 100% rename from Client/src/Components/Link/Link.css rename to Client/src/Components/Link/link.css diff --git a/Client/src/Components/ProgressStepper/ProgressStepper.css b/Client/src/Components/ProgressStepper/index.css similarity index 100% rename from Client/src/Components/ProgressStepper/ProgressStepper.css rename to Client/src/Components/ProgressStepper/index.css diff --git a/Client/src/Components/ProgressStepper/ProgressStepper.jsx b/Client/src/Components/ProgressStepper/index.jsx similarity index 100% rename from Client/src/Components/ProgressStepper/ProgressStepper.jsx rename to Client/src/Components/ProgressStepper/index.jsx diff --git a/Client/src/Components/Section/Section.jsx b/Client/src/Components/Section/index.jsx similarity index 98% rename from Client/src/Components/Section/Section.jsx rename to Client/src/Components/Section/index.jsx index 3ce773c81..9c5415385 100644 --- a/Client/src/Components/Section/Section.jsx +++ b/Client/src/Components/Section/index.jsx @@ -10,7 +10,7 @@ import { } from "@mui/material"; import MenuIcon from "@mui/icons-material/Menu"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; -import Button from "../Button/Button"; +import Button from "../Button/"; import AddIcon from "@mui/icons-material/Add"; /** diff --git a/Client/src/Pages/Demo/Demo.jsx b/Client/src/Pages/Demo/Demo.jsx index 1c294ba5c..8ee00818b 100644 --- a/Client/src/Pages/Demo/Demo.jsx +++ b/Client/src/Pages/Demo/Demo.jsx @@ -1,7 +1,6 @@ import React from "react"; -import Button from "../../Components/Button/Button"; -import Link from "../../Components/Link/Link"; -import ColoredLabel from "../../Components/Label/ColoredLabel"; +import Button from "../../Components/Button/"; +import Link from "../../Components/Link"; import { useTheme, Switch, @@ -14,13 +13,12 @@ import { Tab, Tabs, } from "@mui/material"; -import StatusLabel from "../../Components/Label/StautsLabel"; -import Avatar from "../../Components/Avatar/Avatar"; -import ProgressStepper from "../../Components/ProgressStepper/ProgressStepper"; +import { ColoredLabel, StatusLabel } from "../../Components/Label/"; +import Avatar from "../../Components/Avatar/"; +import ProgressStepper from "../../Components/ProgressStepper"; import avatarImage from "../../assets/Images/avatar_placeholder.png"; -import Section from "../../Components/Section/Section"; +import Section from "../../Components/Section"; import { DataGrid } from "@mui/x-data-grid"; - import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; From 4e12778f001b0e809228fe11a74a7e82ae6aba1c Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Mon, 13 May 2024 15:22:35 -0700 Subject: [PATCH 09/15] Remove seed data, not required --- Server/db/MongoDB.js | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index 3c48a448d..6ad783877 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -1,41 +1,10 @@ const Monitor = require("../models/Monitor"); const mongoose = require("mongoose"); -const seedDatabase = async () => { - const FAKE_MONITOR_DATA = []; - - for (let i = 0; i < 10; i++) { - FAKE_MONITOR_DATA.push( - new Monitor({ - userId: i % 2 === 0 ? 1 : 2, - name: `Monitor ${i}`, - description: `Description for Monitor ${i}`, - url: `https://monitor${i}.com`, - isActive: true, - interval: 60000, - updated_at: new Date(), - created_at: new Date(), - }) - ); - } - - for (const monitor of FAKE_MONITOR_DATA) { - await monitor.save(); - } -}; - const connect = async () => { try { await mongoose.connect(process.env.DB_CONNECTION_STRING); console.log("Connected to MongoDB"); - - const monitors = await Monitor.find(); - if (monitors.length === 0) { - console.log("Seeding database with fake data"); - await seedDatabase(); - } else { - console.log("Database already seeded"); - } } catch (error) { console.error("Failed to connect to MongoDB"); throw error; From 973ed3ce1c5c320490e7b79053781dfcd214bbd8 Mon Sep 17 00:00:00 2001 From: veyselboybay Date: Mon, 13 May 2024 18:47:38 -0400 Subject: [PATCH 10/15] saving changings for pulling updates --- Server/.gitignore | 3 +- Server/index.js | 5 - Server/package-lock.json | 196 ++++++++++++++++++++++++++++++++++++++- Server/package.json | 3 +- 4 files changed, 199 insertions(+), 8 deletions(-) diff --git a/Server/.gitignore b/Server/.gitignore index f469421d1..e27ddd889 100644 --- a/Server/.gitignore +++ b/Server/.gitignore @@ -1,3 +1,4 @@ node_modules .env -utils \ No newline at end of file +utils/sendEmail.js +utils/logger.js \ No newline at end of file diff --git a/Server/index.js b/Server/index.js index 173c2c78d..d379b6f1a 100644 --- a/Server/index.js +++ b/Server/index.js @@ -6,11 +6,6 @@ const { connectDbAndRunServer } = require('./configs/db') require('dotenv').config() // const { sendEmail } = require('./utils/sendEmail') -/** - * NOTES - * Email Service will be added - * Logger Service will be added (Winston or similar) - */ const app = express() diff --git a/Server/package-lock.json b/Server/package-lock.json index 19995c3d5..495d5bc2e 100644 --- a/Server/package-lock.json +++ b/Server/package-lock.json @@ -16,12 +16,31 @@ "helmet": "^7.1.0", "joi": "^17.13.1", "mailersend": "^2.2.0", - "mongoose": "^8.3.3" + "mongoose": "^8.3.3", + "winston": "^3.13.0" }, "devDependencies": { "nodemon": "3.1.0" } }, + "node_modules/@colors/colors": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz", + "integrity": "sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "dependencies": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, "node_modules/@hapi/hoek": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", @@ -94,6 +113,11 @@ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, + "node_modules/@types/triple-beam": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz", + "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==" + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -199,6 +223,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -339,6 +368,37 @@ "node": ">=10" } }, + "node_modules/color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "dependencies": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -347,6 +407,15 @@ "color-support": "bin.js" } }, + "node_modules/colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "dependencies": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -476,6 +545,11 @@ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, + "node_modules/enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -562,6 +636,11 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "node_modules/fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -591,6 +670,11 @@ "node": ">= 0.8" } }, + "node_modules/fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -902,6 +986,11 @@ "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -992,6 +1081,32 @@ "node": ">=12.0.0" } }, + "node_modules/kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "node_modules/logform": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.6.0.tgz", + "integrity": "sha512-1ulHeNPp6k/LD8H91o7VYFBng5i1BDE7HoKxVbZiGFidS1Rj65qcywLxX+pVfAPoQJEjRdvKcusKwOupHCVOVQ==", + "dependencies": { + "@colors/colors": "1.6.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/logform/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -1441,6 +1556,14 @@ "wrappy": "1" } }, + "node_modules/one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "dependencies": { + "fn.name": "1.x.x" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -1594,6 +1717,14 @@ } ] }, + "node_modules/safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1708,6 +1839,14 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/simple-update-notifier": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", @@ -1728,6 +1867,14 @@ "memory-pager": "^1.0.2" } }, + "node_modules/stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==", + "engines": { + "node": "*" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -1796,6 +1943,11 @@ "node": ">=10" } }, + "node_modules/text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1839,6 +1991,14 @@ "node": ">=14" } }, + "node_modules/triple-beam": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", + "integrity": "sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==", + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1919,6 +2079,40 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/winston": { + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.13.0.tgz", + "integrity": "sha512-rwidmA1w3SE4j0E5MuIufFhyJPBDG7Nu71RkZor1p2+qHvJSZ9GYDA81AyleQcZbh/+V6HjeBdfnTZJm9rSeQQ==", + "dependencies": { + "@colors/colors": "^1.6.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.7.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/winston-transport": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.7.0.tgz", + "integrity": "sha512-ajBj65K5I7denzer2IYW6+2bNIVqLGDHqDw3Ow8Ohh+vdW+rv4MZ6eiDvHoKhfJFZ2auyN8byXieDDJ96ViONg==", + "dependencies": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/Server/package.json b/Server/package.json index 53f0fe429..6a082cbdc 100644 --- a/Server/package.json +++ b/Server/package.json @@ -18,7 +18,8 @@ "helmet": "^7.1.0", "joi": "^17.13.1", "mailersend": "^2.2.0", - "mongoose": "^8.3.3" + "mongoose": "^8.3.3", + "winston": "^3.13.0" }, "devDependencies": { "nodemon": "3.1.0" From e6b0b9b2ca9f0fff397c7a4a54d28f9b0ebeb27c Mon Sep 17 00:00:00 2001 From: M M Date: Mon, 13 May 2024 16:12:20 -0700 Subject: [PATCH 11/15] Implemented the Dashboard Menu. --- Client/package-lock.json | 8 +-- Client/package.json | 2 +- Client/src/Components/DashboardMenu/index.css | 15 +++++ Client/src/Components/DashboardMenu/index.jsx | 57 +++++++++++++++++++ Client/src/Pages/Home/index.jsx | 2 + 5 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 Client/src/Components/DashboardMenu/index.css create mode 100644 Client/src/Components/DashboardMenu/index.jsx diff --git a/Client/package-lock.json b/Client/package-lock.json index 79ac309a3..2ad129bd4 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -11,7 +11,7 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@fontsource/roboto": "^5.0.13", - "@mui/icons-material": "^5.15.16", + "@mui/icons-material": "^5.15.17", "@mui/material": "^5.15.16", "@mui/x-data-grid": "7.3.2", "@mui/x-date-pickers": "7.3.2", @@ -1127,9 +1127,9 @@ } }, "node_modules/@mui/icons-material": { - "version": "5.15.16", - "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.16.tgz", - "integrity": "sha512-s8vYbyACzTNZRKv+20fCfVXJwJqNcVotns2EKnu1wmAga6wv2LAo5kB1d5yqQqZlMFtp34EJvRXf7cy8X0tJVA==", + "version": "5.15.17", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.15.17.tgz", + "integrity": "sha512-xVzl2De7IY36s/keHX45YMiCpsIx3mNv2xwDgtBkRSnZQtVk+Gqufwj1ktUxEyjzEhBl0+PiNJqYC31C+n1n6A==", "dependencies": { "@babel/runtime": "^7.23.9" }, diff --git a/Client/package.json b/Client/package.json index 7d5c47a8d..cca88fb31 100644 --- a/Client/package.json +++ b/Client/package.json @@ -13,7 +13,7 @@ "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", "@fontsource/roboto": "^5.0.13", - "@mui/icons-material": "^5.15.16", + "@mui/icons-material": "^5.15.17", "@mui/material": "^5.15.16", "@mui/x-data-grid": "7.3.2", "@mui/x-date-pickers": "7.3.2", diff --git a/Client/src/Components/DashboardMenu/index.css b/Client/src/Components/DashboardMenu/index.css new file mode 100644 index 000000000..37c84655b --- /dev/null +++ b/Client/src/Components/DashboardMenu/index.css @@ -0,0 +1,15 @@ +.icon-container { + display: flex; + align-items: center; + margin-bottom: 12px; + } + + .text-container { + display: flex; + align-items: center; + } + + .dashboard-menu-text { + margin-left: 8px; + } + \ No newline at end of file diff --git a/Client/src/Components/DashboardMenu/index.jsx b/Client/src/Components/DashboardMenu/index.jsx new file mode 100644 index 000000000..70fe3511f --- /dev/null +++ b/Client/src/Components/DashboardMenu/index.jsx @@ -0,0 +1,57 @@ +import language from '@mui/icons-material/language'; +import report_gmailerrorred from '@mui/icons-material/reportGmailerrorred'; +import sensors from '@mui/icons-material/sensors'; +import settings from '@mui/icons-material/settings'; +import all_inclusive from '@mui/icons-material/AllInclusive'; +import SvgIcon from '@mui/material/SvgIcon'; +import './index.css'; + +/** + * DashboardMenu component displays a menu with icons and corresponding text. + * Each menu item consists of an icon and a text label. + * + * @returns {JSX.Element} The JSX element representing the DashboardMenu component. + */ + +function DashboardMenu() { + return ( +
+
+ +
+ Monitors +
+
+ +
+ +
+ Incidents +
+
+ +
+ +
+ Status Pages +
+
+ +
+ +
+ Integrations +
+
+ +
+ +
+ Settings +
+
+
+ ); +} + +export default DashboardMenu; diff --git a/Client/src/Pages/Home/index.jsx b/Client/src/Pages/Home/index.jsx index 8742da6f1..ff5cb454e 100644 --- a/Client/src/Pages/Home/index.jsx +++ b/Client/src/Pages/Home/index.jsx @@ -1,6 +1,7 @@ import DropdownTeamMember from "../../Components/DropdownTeamMember"; import Search from "../../Components/Search"; import "./index.css"; +import DashboardMenu from "../../Components/DashboardMenu"; const Home = () => { return ( @@ -8,6 +9,7 @@ const Home = () => {
Home
+ ); }; From ad2b48bb41ac09f8b31122e9cd095184f86de871 Mon Sep 17 00:00:00 2001 From: veyselboybay Date: Mon, 13 May 2024 19:20:56 -0400 Subject: [PATCH 12/15] Added logging service and emailing function --- README.md | 2 ++ Server/.gitignore | 4 +--- Server/index.js | 11 ++++++---- Server/utils/logger.js | 26 +++++++++++++++++++++++ Server/utils/sendEmail.js | 43 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 Server/utils/logger.js create mode 100644 Server/utils/sendEmail.js diff --git a/README.md b/README.md index 4b5375c8f..3397a324d 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ Configure the server with the following environmental variables | ENV Varialbe Name | Required/Optional | Type | Description | | -------------------- | ----------------- | ------ | --------------------------------- | | DB_CONNECTION_STRING | Required | string | Specfies URL for MongoDB Database | +| MAILERSEND_API_KEY | Required | string | Specfies API KEY for MailerSend service | +| SYSTEM_EMAIL_ADDRESS | Required | string | Specfies System email to be used in emailing service | --- diff --git a/Server/.gitignore b/Server/.gitignore index e27ddd889..1dcef2d9f 100644 --- a/Server/.gitignore +++ b/Server/.gitignore @@ -1,4 +1,2 @@ node_modules -.env -utils/sendEmail.js -utils/logger.js \ No newline at end of file +.env \ No newline at end of file diff --git a/Server/index.js b/Server/index.js index 8487c5012..967a9899b 100644 --- a/Server/index.js +++ b/Server/index.js @@ -5,6 +5,7 @@ const authRouter = require("./routes/authRoute"); const monitorRouter = require("./routes/monitorRoute"); const { connectDbAndRunServer } = require("./configs/db"); require("dotenv").config(); +const logger = require('./utils/logger'); // const { sendEmail } = require('./utils/sendEmail') // ************************** @@ -53,15 +54,17 @@ app.use("/api/v1/monitors", monitorRouter); // Testing email service // app.use('/sendEmail', async (req, res) => { -// const response = sendEmail('veysel.boybay@outlook.com', ['veysel.boybay@bluewavelabs.ca'], 'Testing email service', '

Testing Bluewavelabs

'); +// const response = sendEmail(['veysel.boybay@bluewavelabs.ca'], 'Testing email service', '

Testing Bluewavelabs

'); // console.log(response); // }) //health check app.use("/api/v1/healthy", (req, res) => { - try { - return res.status(200).json({ message: "Healthy" }); - } catch (error) { + try { + logger.info("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 }); } }); diff --git a/Server/utils/logger.js b/Server/utils/logger.js new file mode 100644 index 000000000..a8f65527c --- /dev/null +++ b/Server/utils/logger.js @@ -0,0 +1,26 @@ +const winston = require('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' }), + ], +}); + +module.exports = logger; \ No newline at end of file diff --git a/Server/utils/sendEmail.js b/Server/utils/sendEmail.js new file mode 100644 index 000000000..712a651bd --- /dev/null +++ b/Server/utils/sendEmail.js @@ -0,0 +1,43 @@ +const { MailerSend, EmailParams, Sender, Recipient } = require('mailersend') +const logger = require('../utils/logger') + + +const mailersend = new MailerSend({ + apiKey: process.env.MAILERSEND_API_KEY, +}) +/** + * @function + * @param {[string]} receivers - takes an array of strings + * @param {string} subject - takes a single string + * @param {string} contentHTML - takes a single string that contains HTML + * @returns {JSON} + * @example + * sendEmail(['veysel@bluewavelabs.ca','alex@bluewavelabs.ca','monzer@bluewavelabs.ca'],'Testing Email Servide','

BlueWaveLabs

') + */ +// TODO: from email should be in .env file +const sendEmail = async (receivers,subject,contentHTML) => { + // Sender + const from = process.env.SYSTEM_EMAIL_ADDRESS; + const sender = new Sender(from); + // receivers + let recipients = [] + receivers.map(email => recipients.push(new Recipient(email))); + // Set params + const emailParams = new EmailParams() + .setFrom(sender) + .setTo(recipients) + .setSubject(subject) + .setHtml(contentHTML); + + try { + const response = await mailersend.email.send(emailParams); + logger.info("Email sent to receivers!",{"service":"Email"}) + return response; + + } catch (error) { + logger.error(error.body,{"service":"email"}) + console.log(error.body) + } +} + +module.exports = {sendEmail} \ No newline at end of file From 3ff8c65eb5125ad5c1a7f312b55565c73c18ccbc Mon Sep 17 00:00:00 2001 From: M M Date: Tue, 14 May 2024 10:00:52 -0700 Subject: [PATCH 13/15] Addressed comments. --- Client/src/Components/DashboardMenu/index.jsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Client/src/Components/DashboardMenu/index.jsx b/Client/src/Components/DashboardMenu/index.jsx index 70fe3511f..716d68497 100644 --- a/Client/src/Components/DashboardMenu/index.jsx +++ b/Client/src/Components/DashboardMenu/index.jsx @@ -1,12 +1,13 @@ -import language from '@mui/icons-material/language'; -import report_gmailerrorred from '@mui/icons-material/reportGmailerrorred'; -import sensors from '@mui/icons-material/sensors'; -import settings from '@mui/icons-material/settings'; -import all_inclusive from '@mui/icons-material/AllInclusive'; +import LanguageIcon from "@mui/icons-material/Language"; +import ReportGmailerrorredIcon from "@mui/icons-material/ReportGmailerrorred"; +import SensorsIcon from "@mui/icons-material/Sensors"; +import SettingsIcon from "@mui/icons-material/Settings"; +import AllInclusiveIcon from "@mui/icons-material/AllInclusive"; import SvgIcon from '@mui/material/SvgIcon'; import './index.css'; /** + * @component * DashboardMenu component displays a menu with icons and corresponding text. * Each menu item consists of an icon and a text label. * @@ -17,35 +18,35 @@ function DashboardMenu() { return (
- +
Monitors
- +
Incidents
- +
Status Pages
- +
Integrations
- +
Settings
From a4a61695b0249f2dc919f13124c950114ba4c8f6 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 14 May 2024 11:12:22 -0700 Subject: [PATCH 14/15] Updated gitignore to ignore log files, Refactored Authcontroller to use DB interface, Added Try/Catch to validation to catch uncaught error, Added username optional validation to Joi so it expects a username --- Server/.gitignore | 3 +- Server/controllers/authController.js | 57 +++++++++++-------- Server/db/FakeDb.js | 29 ++++++++++ Server/db/MongoDB.js | 22 ++++++++ Server/index.js | 14 ++--- Server/models/user.js | 83 ++++++++++++++-------------- Server/validation/joi.js | 1 + 7 files changed, 137 insertions(+), 72 deletions(-) diff --git a/Server/.gitignore b/Server/.gitignore index 1dcef2d9f..3fdb59050 100644 --- a/Server/.gitignore +++ b/Server/.gitignore @@ -1,2 +1,3 @@ node_modules -.env \ No newline at end of file +.env +*.log \ No newline at end of file diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index f1a2c6ecd..23d5b8877 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -1,32 +1,43 @@ -const express = require('express') -const UserModel = require('../models/user') -const { authValidation } = require('../validation/joi') +const express = require("express"); +const UserModel = require("../models/user"); +const { authValidation } = require("../validation/joi"); /** * @function - * @param {express.Request} req - * @param {express.Response} res + * @param {express.Request} req + * @param {express.Response} res * @returns {{success: Boolean, msg: String}} */ const registerController = async (req, res) => { - // joi validation - const { error } = await authValidation.validateAsync(req.body); - if (error) return res.status(400).json({ success: false, msg: error.details[0].message }); + // joi validation + try { + await authValidation.validateAsync(req.body); + } catch (error) { + return res + .status(400) + .json({ success: false, msg: error.details[0].message }); + } - // Check if the user exists - const isUser = await UserModel.findOne({ email: req.body.email }); - if (isUser) return res.status(400).json({ success: false, msg: "User already exists!" }) + // Check if the user exists + try { + const isUser = await req.db.getUserByEmail(req, res); + if (isUser) + return res + .status(400) + .json({ success: false, msg: "User already exists!" }); + } catch (error) { + return res.status(500).json({ success: false, msg: error.message }); + } - try { - // Create a new user - const newUser = await UserModel.create({ ...req.body }); + try { + // Create a new user + const newUser = await req.db.insertUser(req, res); + return res.json({ success: true, msg: "User created}", data: newUser }); + // Send an email to user + // Will add this later + } catch (error) { + return res.status(500).json({ success: false, msg: error.message }); + } +}; - // Send an email to user - // Will add this later - } catch (error) { - return res.status(500).json({success:false,msg:'Something went wrong!'}) - } - -} - -module.exports = {registerController} \ No newline at end of file +module.exports = { registerController }; diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js index 6dfa89ac9..8cf05cf89 100644 --- a/Server/db/FakeDb.js +++ b/Server/db/FakeDb.js @@ -21,8 +21,10 @@ // ************************** const Monitor = require("../models/Monitor"); +const UserModel = require("../models/user"); const FAKE_MONITOR_DATA = []; +const USERS = []; for (let i = 0; i < 10; i++) { FAKE_MONITOR_DATA.push( @@ -47,6 +49,31 @@ const connect = async () => { } }; +const insertUser = async (req, res) => { + try { + const newUser = new UserModel({ ...req.body }); + USERS.push(newUser); + return newUser; + } catch (error) { + throw error; + } +}; + +const getUserByEmail = async (req, res) => { + const email = req.body.email; + try { + const idx = USERS.findIndex((user) => { + return user.email === email; + }); + if (idx === -1) { + return null; + } + return USERS[idx]; + } catch (error) { + throw new Error(`User with email ${email} not found`); + } +}; + const getAllMonitors = async () => { return FAKE_MONITOR_DATA; }; @@ -75,6 +102,8 @@ const getMonitorsByUserId = async (userId) => { module.exports = { connect, + insertUser, + getUserByEmail, getAllMonitors, getMonitorById, getMonitorsByUserId, diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index 6ad783877..72f2d1d13 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -1,5 +1,6 @@ const Monitor = require("../models/Monitor"); const mongoose = require("mongoose"); +const UserModel = require("../models/user"); const connect = async () => { try { @@ -11,6 +12,25 @@ const connect = async () => { } }; +const insertUser = async (req, res) => { + try { + const newUser = await UserModel.create({ ...req.body }); + return newUser; + } catch (error) { + throw error; + } +}; + +const getUserByEmail = async (req, res) => { + try { + // Returns null if no user is found + const user = await UserModel.findOne({ email: req.body.email }); + return user; + } catch (error) { + throw error; + } +}; + // Gets all monitors const getAllMonitors = async (req, res) => { try { @@ -43,6 +63,8 @@ const getMonitorsByUserId = async (req, res) => { module.exports = { connect, + insertUser, + getUserByEmail, getAllMonitors, getMonitorById, getMonitorsByUserId, diff --git a/Server/index.js b/Server/index.js index 967a9899b..7e68c72f2 100644 --- a/Server/index.js +++ b/Server/index.js @@ -5,7 +5,7 @@ const authRouter = require("./routes/authRoute"); const monitorRouter = require("./routes/monitorRoute"); const { connectDbAndRunServer } = require("./configs/db"); require("dotenv").config(); -const logger = require('./utils/logger'); +const logger = require("./utils/logger"); // const { sendEmail } = require('./utils/sendEmail') // ************************** @@ -23,7 +23,7 @@ const db = require("./db/MongoDB"); /** * NOTES - * Email Service will be added + * Email Service will be added * Logger Service will be added (Winston or similar) */ @@ -60,11 +60,11 @@ app.use("/api/v1/monitors", monitorRouter); //health check app.use("/api/v1/healthy", (req, res) => { - try { - logger.info("Checking Health of the server.") - return res.status(200).json({ message: "Healthy" }); - } catch (error) { - logger.error(error.message) + try { + logger.info("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 }); } }); diff --git a/Server/models/user.js b/Server/models/user.js index 27d623179..547192293 100644 --- a/Server/models/user.js +++ b/Server/models/user.js @@ -1,45 +1,46 @@ -const mongoose = require('mongoose') -const bcrypt = require('bcrypt') +const mongoose = require("mongoose"); +const bcrypt = require("bcrypt"); const UserSchema = mongoose.Schema({ - username: { - type: String - }, - email: { - type: String, - required: true - }, - password: { - type: String, - required: true - }, - profilePicUrl: { - type: String - }, - isActive: { - type: Boolean, - default: true - }, - isVerified: { - type: Boolean, - default: false - }, - updated_at: { - type: Date, - default: Date.now - }, - created_at: { - type: Date, - default: Date.now - } -}) + username: { + type: String, + }, + email: { + type: String, + required: true, + unique: true, + }, + password: { + type: String, + required: true, + }, + profilePicUrl: { + type: String, + }, + isActive: { + type: Boolean, + default: true, + }, + isVerified: { + type: Boolean, + default: false, + }, + updated_at: { + type: Date, + default: Date.now, + }, + created_at: { + type: Date, + default: Date.now, + }, +}); -UserSchema.pre('save', async function (next) { - if (!this.isModified('password')) { - next() - } - const salt = bcrypt.genSalt(10); - this.password = await bcrypt.hash(this.password, salt); -}) +UserSchema.pre("save", async function (next) { + if (!this.isModified("password")) { + next(); + } + const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait + this.password = bcrypt.hash(this.password, salt); +}); -module.exports = mongoose.model('User',UserSchema) \ No newline at end of file +module.exports = mongoose.model("User", UserSchema); diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 2fb59a377..53427527e 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -2,6 +2,7 @@ const joi = require("joi"); const user = require("../models/user"); const authValidation = joi.object({ + username: joi.string(), email: joi.string().email().required(), password: joi.string().min(8).required(), }); From ca33fcae9193188020f286a7ae2782cd5a2b5c38 Mon Sep 17 00:00:00 2001 From: veyselboybay Date: Tue, 14 May 2024 23:15:29 -0400 Subject: [PATCH 15/15] update on joi schema, added logger docs, and updated user model --- Server/controllers/authController.js | 17 ++++++++++++----- Server/controllers/monitorController.js | 5 +++++ Server/db/MongoDB.js | 4 ++-- Server/models/user.js | 7 ++++++- Server/validation/joi.js | 12 +++++++++--- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 23d5b8877..eba12db7d 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -1,6 +1,7 @@ const express = require("express"); const UserModel = require("../models/user"); -const { authValidation } = require("../validation/joi"); +const { registerValidation } = require("../validation/joi"); +const logger = require('../utils/logger') /** * @function @@ -11,7 +12,7 @@ const { authValidation } = require("../validation/joi"); const registerController = async (req, res) => { // joi validation try { - await authValidation.validateAsync(req.body); + await registerValidation.validateAsync(req.body); } catch (error) { return res .status(400) @@ -21,21 +22,27 @@ const registerController = async (req, res) => { // Check if the user exists try { const isUser = await req.db.getUserByEmail(req, res); - if (isUser) + if (isUser) { + logger.warning("User already exists!", { "service": "auth", "userId": isUser._id }); return res .status(400) .json({ success: false, msg: "User already exists!" }); + } } catch (error) { + logger.error(error.message, { "service": "auth" }); return res.status(500).json({ success: false, msg: error.message }); } + try { // Create a new user const newUser = await req.db.insertUser(req, res); - return res.json({ success: true, msg: "User created}", data: newUser }); - // Send an email to user + // TODO: Send an email to user // Will add this later + logger.info("New user created!", { "service": "auth", "userId": newUser._id }); + return res.json({ success: true, msg: "User created}", data: newUser }); } catch (error) { + logger.error(error.message, { "service": "auth" }); return res.status(500).json({ success: false, msg: error.message }); } }; diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js index dda18d8ff..c85da0f9d 100644 --- a/Server/controllers/monitorController.js +++ b/Server/controllers/monitorController.js @@ -2,6 +2,7 @@ const { getMonitorsByIdValidation, getMonitorsByUserIdValidation, } = require("../validation/joi"); +const logger = require('../utils/logger') // Gets all monitors const getAllMonitors = async (req, res) => { @@ -9,6 +10,7 @@ const getAllMonitors = async (req, res) => { const monitors = await req.db.getAllMonitors(); return res.json({ success: true, msg: "Monitors found", data: monitors }); } catch (error) { + logger.error(error.message, { "service": "monitor" }); return res.status(500).json({ success: false, msg: error.message }); } }; @@ -27,6 +29,7 @@ const getMonitorById = async (req, res) => { const monitor = await req.db.getMonitorById(monitorId); return res.json({ success: true, msg: "Monitor found", data: monitor }); } catch (error) { + logger.error(error.message, { "service": "monitor" }); return res.status(500).json({ success: false, msg: error.message }); } }; @@ -43,12 +46,14 @@ const getMonitorsByUserId = async (req, res) => { try { const userId = req.params.userId; const monitors = await req.db.getMonitorsByUserId(userId); + logger.info(`Monitors for user ${userId} found`, { "service": "monitor", "userId":userId }); return res.json({ success: true, msg: `Monitors for user ${userId} found`, data: monitors, }); } catch (error) { + logger.error(error.message, { "service": "monitor" }); return res.status(500).json({ success: false, msg: error.message }); } }; diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js index 72f2d1d13..e3edbb7e2 100644 --- a/Server/db/MongoDB.js +++ b/Server/db/MongoDB.js @@ -14,7 +14,7 @@ const connect = async () => { const insertUser = async (req, res) => { try { - const newUser = await UserModel.create({ ...req.body }); + const newUser = await UserModel.create({ ...req.body }).select('-password'); return newUser; } catch (error) { throw error; @@ -24,7 +24,7 @@ const insertUser = async (req, res) => { const getUserByEmail = async (req, res) => { try { // Returns null if no user is found - const user = await UserModel.findOne({ email: req.body.email }); + const user = await UserModel.findOne({ email: req.body.email }).select('-password'); return user; } catch (error) { throw error; diff --git a/Server/models/user.js b/Server/models/user.js index 547192293..822c303d8 100644 --- a/Server/models/user.js +++ b/Server/models/user.js @@ -2,8 +2,13 @@ const mongoose = require("mongoose"); const bcrypt = require("bcrypt"); const UserSchema = mongoose.Schema({ - username: { + firstname: { type: String, + required: true, + }, + lastname: { + type: String, + required: true, }, email: { type: String, diff --git a/Server/validation/joi.js b/Server/validation/joi.js index 53427527e..874daff93 100644 --- a/Server/validation/joi.js +++ b/Server/validation/joi.js @@ -1,8 +1,13 @@ const joi = require("joi"); const user = require("../models/user"); -const authValidation = joi.object({ - username: joi.string(), +const loginValidation = joi.object({ + email: joi.string().email().required(), + password: joi.string().min(8).required(), +}); +const registerValidation = joi.object({ + firstname: joi.string().required(), + lastname: joi.string().required(), email: joi.string().email().required(), password: joi.string().min(8).required(), }); @@ -16,7 +21,8 @@ const getMonitorsByUserIdValidation = joi.object({ }); module.exports = { - authValidation, + loginValidation, + registerValidation, getMonitorsByIdValidation, getMonitorsByUserIdValidation, };