+ );
+}
+
+export default PlayGroundTooltips;
diff --git a/Client/src/Pages/PlayGround/PlayGround-announcements.jsx b/Client/src/Pages/PlayGround/PlayGround-announcements.jsx
new file mode 100644
index 000000000..aa4bd1913
--- /dev/null
+++ b/Client/src/Pages/PlayGround/PlayGround-announcements.jsx
@@ -0,0 +1,73 @@
+import "./playGround.css";
+import React from "react";
+import AnnouncementsDualButtonWithIcon from "../../Components/Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
+import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
+import ComplexAlert from "../../Components/Icons/ComplexAlert/ComplexAlert";
+import AnnouncementsMessageBar from "../../Components/Announcements/AnnouncementsMessageBar/AnnouncementsMessageBar";
+import AnnouncementsDualButton from "../../Components/Announcements/AnnouncementsDualButton/AnnouncementsDualButton";
+import AnnouncementUpdateSubscription from "../../Components/Announcements/AnnouncementUpdateSubscription/AnnouncementUpdateSubscription";
+
+function PlayGroundAnnouncements() {
+ return (
+
+
+
+
+ }
+ subject="We’ve just released a new feature"
+ body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
+ pariatur, ipsum dolor."
+ esc="Dismiss"
+ primary="View changes"
+ />
+
+
+ }
+ subject="There was a problem with that action"
+ body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
+ pariatur, ipsum dolor."
+ esc="Dismiss"
+ primary="Learn more"
+ />
+
+
+ }
+ subject="Successfully updated profile"
+ body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
+ pariatur, ipsum dolor."
+ esc="Dismiss"
+ primary="Learn more"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default PlayGroundAnnouncements;
diff --git a/Client/src/Pages/PlayGround/PlayGround.jsx b/Client/src/Pages/PlayGround/PlayGround.jsx
new file mode 100644
index 000000000..b5e439aed
--- /dev/null
+++ b/Client/src/Pages/PlayGround/PlayGround.jsx
@@ -0,0 +1,126 @@
+import React from "react";
+import EmailTextField from "../../Components/TextFields/Email/EmailTextField";
+import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
+import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
+import WebsiteTextField from "../../Components/TextFields/Website/WebsiteTextField";
+import DescriptionTextField from "../../Components/TextFields/Description/DescriptionTextField";
+import AnnouncementsDualButton from "../../Components/Announcements/AnnouncementsDualButton/AnnouncementsDualButton";
+import AnnouncementsDualButtonWithIcon from "../../Components/Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
+import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
+import ComplexAlert from "../../Components/Icons/ComplexAlert/ComplexAlert";
+import AnnouncementsMessageBar from "../../Components/Announcements/AnnouncementsMessageBar/AnnouncementsMessageBar";
+import AnnouncementUpdateSubscription from "../../Components/Announcements/AnnouncementUpdateSubscription/AnnouncementUpdateSubscription";
+import PlayGroundCharts from "./PlayGround-Charts";
+import PlayGroundPopupModals from "./PlayGround-Popup-Modals";
+import PlayGroundTooltips from "./PlayGround-Tooltips";
+
+// This Component is just for the development and test
+// purposes and just to see what my components look like while development
+// and will be removed in the end
+function PlayGround() {
+ return (
+
+ {/* Default state of Email Text Fields */}
+
+ {/* Email Text Field when validation error occures */}
+ }
+ helperText="This is an error message"
+ />
+ {/* Email Text Field while mouse hover */}
+ }
+ helperText="This is a hint text to help user."
+ />
+
+
+ {/* Now, illustration of the Website text fields */}
+
+
+
+
+
+ {/* Now, illustration of the Description text fields */}
+
+
+
+
+
+
+ }
+ subject="We’ve just released a new feature"
+ body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
+ pariatur, ipsum dolor."
+ esc="Dismiss"
+ primary="View changes"
+ />
+
+ }
+ subject="There was a problem with that action"
+ body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
+ pariatur, ipsum dolor."
+ esc="Dismiss"
+ primary="Learn more"
+ />
+
+ }
+ subject="Successfully updated profile"
+ body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid
+ pariatur, ipsum dolor."
+ esc="Dismiss"
+ primary="Learn more"
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default PlayGround;
diff --git a/Client/src/Pages/PlayGround/playGround.css b/Client/src/Pages/PlayGround/playGround.css
new file mode 100644
index 000000000..cd6d5e87b
--- /dev/null
+++ b/Client/src/Pages/PlayGround/playGround.css
@@ -0,0 +1,19 @@
+.play-ground-charts {
+ display: flex;
+}
+
+.play-ground-charts-spacing {
+ height: 110px;
+}
+
+.tooltip-playground {
+ margin: auto;
+ padding: 5%;
+ width: 5%;
+ justify-content: center;
+ text-align: center;
+}
+
+.playground {
+ padding: 40px;
+}
diff --git a/Client/src/Utils/Theme.js b/Client/src/Utils/Theme.js
index 99a442dfb..068e24572 100644
--- a/Client/src/Utils/Theme.js
+++ b/Client/src/Utils/Theme.js
@@ -16,6 +16,10 @@ const labelRed = "#F04438";
//Section colros
const sectionBorder = "#D0D5DD";
const sectionBg = "#F8F9F8";
+
+// Global Font Family
+const fontFamilyDefault = '"Roboto", "Helvetica", "Arial", sans-serif';
+
const theme = createTheme({
palette: {
primary: {
@@ -48,6 +52,11 @@ const theme = createTheme({
bgColor: sectionBg,
},
},
+ font :{
+ default: {
+ font: fontFamilyDefault,
+ },
+ },
shape: {
borderRadius: 4,
},
diff --git a/Client/src/assets/icons/icon-red-error-0.svg b/Client/src/assets/icons/icon-red-error-0.svg
new file mode 100644
index 000000000..f052582ba
--- /dev/null
+++ b/Client/src/assets/icons/icon-red-error-0.svg
@@ -0,0 +1,3 @@
+
diff --git a/README.md b/README.md
index 3397a324d..92e1b7bb1 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,11 @@ BlueWave uptime monitoring application
- Clone this repository to your local machine
+1. [Installation (Client)](#client)
+1. [Installation (Server)](#server)
+1. [Configuration(Server)](#config-server)
+1. [Endpoints](#endpoints)
+
---
### Client
@@ -30,15 +35,17 @@ BlueWave uptime monitoring application
---
-#### Configuration
+#### Configuration {#config-server}
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 |
+| ENV Varialbe Name | Required/Optional | Type | Description | Accepted Values |
+| -------------------- | ----------------- | ------- | ---------------------------------------------------- | ------------------- |
+| DB_TYPE | Optional | string | Specify DB to use | `MongoDB \| FakeDB` |
+| DB_CONNECTION_STRING | Required | string | Specfies URL for MongoDB Database | |
+| PORT | Optional | Integer | Specifies Port for Server | |
+| 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 | |
---
@@ -64,9 +71,28 @@ Example:
{success: true, msg: "Successful Request", data: {test: testData}}
```
+---
+
##### Data Types
-###### Monitor
+
+User
+
+| Name | Type | Notes |
+| ------------- | --------- | --------------------- |
+| firstname | `string` | First name |
+| lastname | `string` | Last name |
+| email | `string` | User's email |
+| profilePicUrl | `string` | URL to User's picture |
+| isActive | `boolean` | Default to `true` |
+| isVerified | `boolean` | Default to `false` |
+| updated_at | `Date` | Last update time |
+| created_at | `Date` | Time created at |
+
+
+
+
+Monitor
| Name | Type | Notes |
| ----------- | --------- | ---------------------------------------- |
@@ -79,51 +105,186 @@ Example:
| 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
+
+POST/api/v1/auth/register
-###### Response
+##### Method/Headers
-| Status Code | Type | Description |
-| ----------- | --------------------- | ------------------------------ |
-| 200 | Response with Payload | Response with a single monitor |
+> | Method/Headers | Value |
+> | -------------- | ---------------- |
+> | Method | POST |
+> | content-type | application/json |
-###### Payload
+##### Body
-| Type | Notes |
-| --------- | --------------------------------------------------- |
-| `Monitor` | Single monitor with the id in the request parameter |
+> | Name | Type | Notes |
+> | --------- | -------- | ------------------- |
+> | firstname | `string` | |
+> | lastname | `string` |
+> | email | `string` | Valid email address |
+> | password | `string` | Min 8 chars |
+
+##### Response Payload
+
+> | Type | Notes |
+> | ------ | ---------- |
+> | `User` | User model |
+
+##### Sample CURL request
+
+```
+curl --request POST \
+ --url http://localhost:5000/api/v1/auth/register \
+ --header 'Content-Type: application/json' \
+ --data '{
+ "firstname" : "User First Name",
+ "lastname": "User Last Name",
+ "email" : "user@gmail.com",
+ "password": "user_password"
+}'
+```
+
+##### Sample Response
+
+```json
+{
+ "success": true,
+ "msg": "User created}",
+ "data": {
+ "_id": "6645079aae0b439371913972",
+ "firstname": "User First Name",
+ "lastname": "User Last Name",
+ "email": "user@gmail.com",
+ "isActive": true,
+ "isVerified": false,
+ "updated_at": "2024-05-15T19:06:02.720Z",
+ "created_at": "2024-05-15T19:06:02.720Z",
+ "__v": 0
+ }
+}
+```
+
+
---
-##### GET /api/v1/monitors/user/:userId
+
+POST/api/v1/auth/login
-| Status Code | Type | Description |
-| ----------- | --------------------- | -------------------------------- |
-| 200 | Response with Payload | Response with a list of monitors |
+##### Method/Headers
-###### Payload
+> | Method/Headers | Value |
+> | -------------- | ---------------- |
+> | Method | POST |
+> | content-type | application/json |
-| Type | Notes |
-| ---------------- | ------------------------------------------------------------------ |
-| `Array[Monitor]` | Array of monitors created by user with userId specified in request |
+##### Body
+
+> | Name | Type | Notes |
+> | -------- | -------- | ------------------- |
+> | email | `string` | Valid email address |
+> | password | `string` | Min 8 chars |
+
+##### Response Payload
+
+> | Type | Notes |
+> | ------ | ---------- |
+> | `User` | User model |
+
+##### Sample CURL request
+
+```
+curl --request POST \
+ --url http://localhost:5000/api/v1/auth/login \
+ --header 'Content-Type: application/json' \
+ --data '{
+ "email" : "user@gmail.com",
+ "password": "user_password"
+}'
+```
+
+##### Sample response
+
+```json
+{
+ "success": true,
+ "msg": "Found user",
+ "data": {
+ "_id": "6644fb9bae0b439371913969",
+ "firstname": "User First Name",
+ "lastname": "User Last Name",
+ "email": "user@gmail.com",
+ "isActive": true,
+ "isVerified": false,
+ "updated_at": "2024-05-15T18:14:51.049Z",
+ "created_at": "2024-05-15T18:14:51.049Z",
+ "__v": 0
+ }
+}
+```
+
+
+
+---
+
+
+GET/api/v1/monitors
+
+##### Method/Headers
+
+> | Method/Headers | Value |
+> | -------------- | ----- |
+> | Method | GET |
+
+##### Response Payload
+
+> | Type | Notes |
+> | ---------------- | ----------------- |
+> | `Array[Monitor]` | Array of monitors |
+
+
+
+---
+
+
+GET/api/v1/monitor/{id}
+
+###### Method/Headers
+
+> | Method/Headers | Value |
+> | -------------- | ----- |
+> | Method | GET |
+
+###### Response Payload
+
+> | Type | Notes |
+> | --------- | --------------------------------------------------- |
+> | `Monitor` | Single monitor with the id in the request parameter |
+
+
+
+---
+
+
+GET/api/v1/monitors/user/{userId}
+
+###### Method/Headers
+
+> | Method/Headers | Value |
+> | -------------- | ----- |
+> | Method | GET |
+
+###### Request Payload
+
+> | Type | Notes |
+> | ---------------- | ------------------------------------------------------------------ |
+> | `Array[Monitor]` | Array of monitors created by user with userId specified in request |
+
+
## Contributors
diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js
index eba12db7d..e8fdc739e 100644
--- a/Server/controllers/authController.js
+++ b/Server/controllers/authController.js
@@ -1,7 +1,7 @@
const express = require("express");
const UserModel = require("../models/user");
-const { registerValidation } = require("../validation/joi");
-const logger = require('../utils/logger')
+const { registerValidation, loginValidation } = require("../validation/joi");
+const logger = require("../utils/logger");
/**
* @function
@@ -23,28 +23,73 @@ const registerController = async (req, res) => {
try {
const isUser = await req.db.getUserByEmail(req, res);
if (isUser) {
- logger.warning("User already exists!", { "service": "auth", "userId": isUser._id });
+ logger.error("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" });
+ 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);
// 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 });
+ logger.info("New user created!", { service: "auth", userId: newUser._id });
+ return res
+ .status(200)
+ .json({ success: true, msg: "User created}", data: newUser });
} catch (error) {
- logger.error(error.message, { "service": "auth" });
+ logger.error(error.message, { service: "auth" });
return res.status(500).json({ success: false, msg: error.message });
}
};
-module.exports = { registerController };
+// **************************
+// Handles logging in a user
+// Returns a user at the moment, but will likely return a JWT in the future
+// **************************
+const loginController = async (req, res) => {
+ try {
+ // Validate input
+ await loginValidation.validateAsync(req.body);
+
+ // Check if user exists
+ const user = await req.db.getUserByEmail(req, res);
+
+ // If user not found, throw an error
+ if (!user) {
+ throw new Error("User not found!");
+ }
+ // Compare password
+ const match = await user.comparePassword(req.body.password);
+ console.log(user);
+ if (!match) {
+ throw new Error("Password does not match!");
+ }
+
+ // Copy user to remove password. More memory, but better than mutating user?
+ // We can move this to the DB layer if we want
+ // Probably not neccessary at all as we'll likely return a JWT instead of a user
+ const userWithoutPassword = { ...user._doc };
+ delete userWithoutPassword.password;
+
+ // Happy path, return user
+ // In the future we'll probably return a JWT instead of a user
+ return res
+ .status(200)
+ .json({ success: true, msg: "Found user", data: userWithoutPassword });
+ } catch (error) {
+ // Anything else should be an error
+ logger.error(error.message, { service: "auth" });
+ return res.status(500).json({ success: false, msg: error.message });
+ }
+};
+
+module.exports = { registerController, loginController };
diff --git a/Server/controllers/monitorController.js b/Server/controllers/monitorController.js
index c85da0f9d..8b2f72ab1 100644
--- a/Server/controllers/monitorController.js
+++ b/Server/controllers/monitorController.js
@@ -1,26 +1,43 @@
const {
- getMonitorsByIdValidation,
+ getMonitorByIdValidation,
getMonitorsByUserIdValidation,
+ monitorValidation,
} = require("../validation/joi");
-const logger = require('../utils/logger')
-// Gets all monitors
+const logger = require("../utils/logger");
+const Monitor = require("../models/Monitor");
+
+/**
+ * Returns all monitors
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
const getAllMonitors = async (req, res) => {
try {
const monitors = await req.db.getAllMonitors();
return res.json({ success: true, msg: "Monitors found", data: monitors });
} catch (error) {
- logger.error(error.message, { "service": "monitor" });
+ logger.error(error.message, { service: "monitor" });
return res.status(500).json({ success: false, msg: error.message });
}
};
-// Get a monitor by ID
+/**
+ * Returns monitor with matching ID
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
const getMonitorById = async (req, res) => {
- const { error } = getMonitorsByIdValidation.validate(req.params);
+ const { error } = getMonitorByIdValidation.validate(req.params);
if (error) {
return res
- .status(400)
+ .status(422)
.json({ success: false, msg: error.details[0].message });
}
@@ -29,33 +46,143 @@ 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" });
+ logger.error(error.message, { service: "monitor" });
return res.status(500).json({ success: false, msg: error.message });
}
};
-// Gets a monitor by user ID
+/**
+ * Returns all monitors belong to User with UserID
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
const getMonitorsByUserId = async (req, res) => {
const { error } = getMonitorsByUserIdValidation.validate(req.params);
if (error) {
return res
- .status(400)
+ .status(422)
.json({ success: false, msg: error.details[0].message });
}
try {
const userId = req.params.userId;
const monitors = await req.db.getMonitorsByUserId(userId);
- logger.info(`Monitors for user ${userId} found`, { "service": "monitor", "userId":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" });
+ logger.error(error.message, { service: "monitor" });
return res.status(500).json({ success: false, msg: error.message });
}
};
-module.exports = { getAllMonitors, getMonitorById, getMonitorsByUserId };
+/**
+ * Creates a new monitor
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
+
+const createMonitor = async (req, res) => {
+ const { error } = monitorValidation.validate(req.body);
+ if (error) {
+ return res
+ .status(422)
+ .json({ success: false, msg: error.details[0].message });
+ }
+
+ try {
+ const monitor = await req.db.createMonitor(req, res);
+ return res
+ .status(201)
+ .json({ success: true, msg: "Monitor created", data: monitor });
+ } catch (error) {
+ logger.error(error.message, { service: "monitor" });
+ return res.status(500).json({ success: false, msg: error.message });
+ }
+};
+
+/**
+ * Delete a monitor by ID
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
+
+const deleteMonitor = async (req, res) => {
+ const { error } = getMonitorByIdValidation.validate(req.params);
+ if (error) {
+ return res
+ .status(422)
+ .json({ success: false, msg: error.details[0].message });
+ }
+ try {
+ const monitor = await req.db.deleteMonitor(req, res);
+ /**
+ * TODO
+ * We should remove all checks and alerts associated with this monitor
+ * when it is deleted so there is no orphaned data
+ * We also need to make sure to stop all running services for this monitor
+ */
+ return res.status(204).json({ success: true, msg: "Monitor deleted" });
+ } catch (error) {
+ logger.error(error.message, { service: "monitor" });
+ return res.status(500).json({ success: false, msg: error.message });
+ }
+};
+
+/**
+ * Edit a monitor by ID
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
+const editMonitor = async (req, res) => {
+ let paramError = getMonitorByIdValidation.validate(req.params);
+ if (paramError.error) {
+ return res
+ .status(422)
+ .json({ success: false, msg: paramError.error.details[0].message });
+ }
+
+ let { error } = monitorValidation.validate(req.body);
+ if (error) {
+ return res
+ .status(422)
+ .json({ success: false, msg: error.details[0].message });
+ }
+
+ try {
+ const editedMonitor = await req.db.editMonitor(req, res);
+ return res
+ .status(200)
+ .json({ success: true, msg: "Monitor edited", data: editedMonitor });
+ } catch (error) {
+ logger.error(error.message, { service: "monitor" });
+ return res.status(500).json({ success: false, msg: error.message });
+ }
+};
+
+module.exports = {
+ getAllMonitors,
+ getMonitorById,
+ getMonitorsByUserId,
+ createMonitor,
+ deleteMonitor,
+ editMonitor,
+};
diff --git a/Server/db/FakeDb.js b/Server/db/FakeDb.js
index 8cf05cf89..b2abd919e 100644
--- a/Server/db/FakeDb.js
+++ b/Server/db/FakeDb.js
@@ -22,25 +22,11 @@
const Monitor = require("../models/Monitor");
const UserModel = require("../models/user");
+const bcrypt = require("bcrypt");
-const FAKE_MONITOR_DATA = [];
+let FAKE_MONITOR_DATA = [];
const USERS = [];
-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 connect = async () => {
try {
await console.log("Connected to FakeDB");
@@ -52,6 +38,8 @@ const connect = async () => {
const insertUser = async (req, res) => {
try {
const newUser = new UserModel({ ...req.body });
+ const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
+ newUser.password = await bcrypt.hash(newUser.password, salt); // hash is also async, need to eitehr await or use hashSync
USERS.push(newUser);
return newUser;
} catch (error) {
@@ -96,10 +84,45 @@ const getMonitorsByUserId = async (userId) => {
if (userMonitors.length === 0) {
throw new Error(`Monitors for user ${userId} not found`);
}
-
return userMonitors;
};
+const createMonitor = async (req, res) => {
+ const monitor = new Monitor(req.body);
+ monitor.createdAt = Date.now();
+ monitor.updatedAt = Date.now();
+ FAKE_MONITOR_DATA.push(monitor);
+ return monitor;
+};
+
+const deleteMonitor = async (req, res) => {
+ const monitorId = req.params.monitorId;
+ try {
+ const monitor = getMonitorById(monitorId);
+ FAKE_MONITOR_DATA = FAKE_MONITOR_DATA.filter((monitor) => {
+ return monitor.id !== monitorId;
+ });
+ return monitor;
+ } catch (error) {
+ throw error;
+ }
+};
+
+const editMonitor = async (req, res) => {
+ const monitorId = req.params.monitorId;
+ const idx = FAKE_MONITOR_DATA.findIndex((monitor) => {
+ return monitor._id.toString() === monitorId;
+ });
+ const oldMonitor = FAKE_MONITOR_DATA[idx];
+ const editedMonitor = new Monitor({ ...req.body });
+ editedMonitor._id = oldMonitor._id;
+ editedMonitor.userId = oldMonitor.userId;
+ editedMonitor.updatedAt = Date.now();
+ editedMonitor.createdAt = oldMonitor.createdAt;
+ FAKE_MONITOR_DATA[idx] = editedMonitor;
+ return FAKE_MONITOR_DATA[idx];
+};
+
module.exports = {
connect,
insertUser,
@@ -107,4 +130,7 @@ module.exports = {
getAllMonitors,
getMonitorById,
getMonitorsByUserId,
+ createMonitor,
+ deleteMonitor,
+ editMonitor,
};
diff --git a/Server/db/MongoDB.js b/Server/db/MongoDB.js
index e3edbb7e2..8fcc1dced 100644
--- a/Server/db/MongoDB.js
+++ b/Server/db/MongoDB.js
@@ -12,36 +12,70 @@ const connect = async () => {
}
};
+/**
+ * Insert a User
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
const insertUser = async (req, res) => {
try {
- const newUser = await UserModel.create({ ...req.body }).select('-password');
- return newUser;
+ const newUser = new UserModel({ ...req.body });
+ await newUser.save();
+ return await UserModel.findOne({ _id: newUser._id }).select("-password"); // .select() doesn't work with create, need to save then find
} catch (error) {
throw error;
}
};
+/**
+ * Get User by Email
+ * Gets a user by Email. Not sure if we'll ever need this except for login.
+ * If not needed except for login, we can move password comparison here
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
const getUserByEmail = async (req, res) => {
try {
// Returns null if no user is found
- const user = await UserModel.findOne({ email: req.body.email }).select('-password');
- return user;
+ // Need the password to be able to compare, removed .select()
+ // We can strip the hash before returing the user
+ return await UserModel.findOne({ email: req.body.email });
} catch (error) {
throw error;
}
};
-// Gets all monitors
+/**
+ * Get all monitors
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise>}
+ * @throws {Error}
+ */
const getAllMonitors = async (req, res) => {
try {
- const monitors = await Monitor.find();
+ const monitors = await Monitor.find({ isActive: true });
return monitors;
} catch (error) {
throw error;
}
};
-// Get a monitor by ID
+/**
+ * Get a monitor by ID
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
const getMonitorById = async (req, res) => {
try {
const monitor = await Monitor.findById(req.params.monitorId);
@@ -51,7 +85,14 @@ const getMonitorById = async (req, res) => {
}
};
-// Gets a monitor by user ID
+/**
+ * Get monitors by UserID
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise>}
+ * @throws {Error}
+ */
const getMonitorsByUserId = async (req, res) => {
try {
const monitors = await Monitor.find({ userId: req.params.userId });
@@ -61,6 +102,68 @@ const getMonitorsByUserId = async (req, res) => {
}
};
+/**
+ * Create a monitor
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
+const createMonitor = async (req, res) => {
+ try {
+ const monitor = new Monitor({ ...req.body });
+ await monitor.save();
+ return monitor;
+ } catch (error) {
+ throw error;
+ }
+};
+
+/**
+ * Delete a monitor by ID
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
+const deleteMonitor = async (req, res) => {
+ const monitorId = req.params.monitorId;
+ try {
+ const monitor = await Monitor.findByIdAndDelete(monitorId);
+ if (!monitor) {
+ throw new Error(`Monitor with id ${monitorId} not found`);
+ }
+ return monitor;
+ } catch (error) {
+ throw error;
+ }
+};
+
+/**
+ * Edit a monitor by ID
+ * @async
+ * @param {Express.Request} req
+ * @param {Express.Response} res
+ * @returns {Promise}
+ * @throws {Error}
+ */
+const editMonitor = async (req, res) => {
+ const candidateId = req.params.monitorId;
+ const candidateMonitor = req.body;
+ try {
+ const editedMonitor = await Monitor.findByIdAndUpdate(
+ candidateId,
+ candidateMonitor,
+ { new: true }
+ );
+ return editedMonitor;
+ } catch (error) {
+ throw error;
+ }
+};
+
module.exports = {
connect,
insertUser,
@@ -68,4 +171,7 @@ module.exports = {
getAllMonitors,
getMonitorById,
getMonitorsByUserId,
+ createMonitor,
+ deleteMonitor,
+ editMonitor,
};
diff --git a/Server/index.js b/Server/index.js
index 7e68c72f2..4d2765cd8 100644
--- a/Server/index.js
+++ b/Server/index.js
@@ -17,10 +17,19 @@ const logger = require("./utils/logger");
// const db = require("./db/FakeDb");
//
// MongoDB
-const db = require("./db/MongoDB");
+// const db = require("./db/MongoDB");
//
// **************************
+const DB_TYPE = {
+ MongoDB: () => require("./db/MongoDB"),
+ FakedDB: () => require("./db/FakeDb"),
+};
+
+const db = DB_TYPE[process.env.DB_TYPE]
+ ? DB_TYPE[process.env.DB_TYPE]()
+ : require("./db/FakeDb");
+
/**
* NOTES
* Email Service will be added
diff --git a/Server/models/Monitor.js b/Server/models/Monitor.js
index 513d4e2be..4aaad1497 100644
--- a/Server/models/Monitor.js
+++ b/Server/models/Monitor.js
@@ -1,37 +1,36 @@
const mongoose = require("mongoose");
-const MonitorSchema = mongoose.Schema({
- userId: {
- type: String,
+const MonitorSchema = mongoose.Schema(
+ {
+ userId: {
+ type: mongoose.Schema.Types.ObjectId,
+ ref: "User",
+ immutable: true,
+ },
+ 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,
+ },
},
- 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,
- },
-});
+ {
+ timestamps: true,
+ }
+);
module.exports = mongoose.model("Monitor", MonitorSchema);
diff --git a/Server/models/user.js b/Server/models/user.js
index 822c303d8..02d0d53b3 100644
--- a/Server/models/user.js
+++ b/Server/models/user.js
@@ -45,7 +45,13 @@ UserSchema.pre("save", async function (next) {
next();
}
const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
- this.password = bcrypt.hash(this.password, salt);
+ this.password = await bcrypt.hash(this.password, salt); // hash is also async, need to eitehr await or use hashSync
+ next();
});
+UserSchema.methods.comparePassword = function (submittedPassword) {
+ console.log(submittedPassword, this.password);
+ return bcrypt.compare(submittedPassword, this.password);
+};
+
module.exports = mongoose.model("User", UserSchema);
diff --git a/Server/routes/authRoute.js b/Server/routes/authRoute.js
index c778927e5..48a173b92 100644
--- a/Server/routes/authRoute.js
+++ b/Server/routes/authRoute.js
@@ -1,6 +1,10 @@
-const router = require('express').Router();
-const { registerController } = require('../controllers/authController')
+const router = require("express").Router();
+const {
+ registerController,
+ loginController,
+} = require("../controllers/authController");
-router.post('/register', registerController)
+router.post("/register", registerController);
+router.post("/login", loginController);
-module.exports = router;
\ No newline at end of file
+module.exports = router;
diff --git a/Server/routes/monitorRoute.js b/Server/routes/monitorRoute.js
index e12982905..5f2ce4bb5 100644
--- a/Server/routes/monitorRoute.js
+++ b/Server/routes/monitorRoute.js
@@ -1,7 +1,11 @@
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);
+router.post("/", monitorController.createMonitor);
+router.post("/delete/:monitorId", monitorController.deleteMonitor);
+router.post("/edit/:monitorId", monitorController.editMonitor);
module.exports = router;
diff --git a/Server/validation/joi.js b/Server/validation/joi.js
index 874daff93..ecc19253e 100644
--- a/Server/validation/joi.js
+++ b/Server/validation/joi.js
@@ -12,7 +12,7 @@ const registerValidation = joi.object({
password: joi.string().min(8).required(),
});
-const getMonitorsByIdValidation = joi.object({
+const getMonitorByIdValidation = joi.object({
monitorId: joi.string().required(),
});
@@ -20,9 +20,20 @@ const getMonitorsByUserIdValidation = joi.object({
userId: joi.string().required(),
});
+const monitorValidation = joi.object({
+ _id: joi.string(),
+ userId: joi.string().required(),
+ name: joi.string().required(),
+ description: joi.string().required(),
+ url: joi.string().uri().required(),
+ isActive: joi.boolean(),
+ interval: joi.number(),
+});
+
module.exports = {
loginValidation,
registerValidation,
- getMonitorsByIdValidation,
+ getMonitorByIdValidation,
getMonitorsByUserIdValidation,
+ monitorValidation,
};