Merge remote-tracking branch 'upstream/master' into feat/details-pg

This commit is contained in:
M M committed 2024-07-07 17:57:20 -07:00
commit 41b65c1f3b
13 files changed
+388 -420

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
.alert {
margin: 0;
width: fit-content;
padding: var(--env-var-spacing-1-plus);
font-size: var(--env-var-font-size-medium);
}
.alert .MuiStack-root {
max-width: 85%;
}
+60
View File
@@ -0,0 +1,60 @@
import PropTypes from "prop-types";
import { useTheme } from "@emotion/react";
import { Box, Stack } from "@mui/material";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import ErrorOutlineOutlinedIcon from "@mui/icons-material/ErrorOutlineOutlined";
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
import "./index.css";
/**
* Icons mapping for different alert variants.
* @type {Object<string, JSX.Element>}
*/
const icons = {
info: <InfoOutlinedIcon />,
error: <ErrorOutlineOutlinedIcon />,
warning: <WarningAmberOutlinedIcon />,
};
/**
* @param {Object} props
* @param {'info' | 'error' | 'warning'} props.variant - The type of alert.
* @param {string} [props.title] - The title of the alert.
* @param {string} [props.body] - The body text of the alert.
* @returns {JSX.Element}
*/
const Alert = ({ variant, title, body }) => {
const theme = useTheme();
const { bg, border, color } = theme.alert[variant];
const icon = icons[variant];
return (
<Stack
direction="row"
justifyContent="flex-start"
className="alert row-stack"
gap={theme.gap.ml}
sx={{
backgroundColor: bg,
border: `solid 1px ${border}`,
borderRadius: `${theme.shape.borderRadius}px`,
}}
>
{icon && <Box sx={{ color: color }}>{icon}</Box>}
<Stack direction="column" gap="2px" sx={{ flex: 1, color: color }}>
{title && <Box sx={{ fontWeight: "700" }}>{title}</Box>}
{body && <Box sx={{ fontWeight: "400" }}>{body}</Box>}
</Stack>
</Stack>
);
};
Alert.propTypes = {
variant: PropTypes.oneOf(["info", "error", "warning"]).isRequired,
title: PropTypes.string,
body: PropTypes.string,
};
export default Alert;
@@ -1,14 +1,11 @@
import TabPanel from "@mui/lab/TabPanel";
import React, { useState } from "react";
// import AnnouncementsDualButtonWithIcon from "../../Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
import AnnouncementsDualButtonWithIcon from "../../Toast/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
import { useTheme } from "@emotion/react";
import { Box, Divider, Stack, Typography } from "@mui/material";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
import ButtonSpinner from "../../ButtonSpinner";
import PasswordTextField from "../../TextFields/Password/PasswordTextField";
import { editPasswordValidation } from "../../../Validation/validation";
import Alert from "../../Alert";
/**
* PasswordPanel component manages the form for editing password.
@@ -96,13 +93,9 @@ const PasswordPanel = () => {
spellCheck="false"
>
<div className="edit-password-form__wrapper">
<AnnouncementsDualButtonWithIcon
icon={
<InfoOutlinedIcon
style={{ fill: theme.palette.secondary.main }}
/>
}
subject="SSO login"
<Alert
variant="info"
title="SSO login"
body="Since you logged in via SSO, you cannot reset or modify your password."
/>
</div>
@@ -181,8 +174,8 @@ const PasswordPanel = () => {
<div className="edit-password-form__wrapper">
<Stack></Stack>
<Box sx={{ flex: 1 }}>
<AnnouncementsDualButtonWithIcon
icon={<WarningAmberOutlinedIcon style={{ fill: "#f79009" }} />}
<Alert
variant="warning"
body="New password must contain at least 8 characters and must have at least one uppercase letter, one number and one symbol."
/>
</Box>
+6 -30
View File
@@ -28,8 +28,7 @@
margin: 50px 0;
}
.account .MuiDivider-root,
.account .MuiOutlinedInput-root fieldset,
.account .announcement-tile {
.account .MuiOutlinedInput-root fieldset {
border-color: var(--env-var-color-29);
}
.account [class$="-form"] {
@@ -42,9 +41,9 @@
justify-content: space-between;
align-items: flex-start;
}
.edit-profile-form__wrapper .MuiStack-root:not(.row-stack),
.edit-password-form__wrapper .MuiStack-root:not(.row-stack),
.edit-organization-form__wrapper .MuiStack-root:not(.row-stack) {
.edit-profile-form__wrapper>.MuiStack-root:not(.row-stack),
.edit-password-form__wrapper>.MuiStack-root:not(.row-stack),
.edit-organization-form__wrapper>.MuiStack-root:not(.row-stack) {
flex-direction: column;
gap: 8px;
margin-right: 10px;
@@ -74,36 +73,13 @@
#input-team-member {
padding: 10px;
}
.edit-password-form__wrapper > .announcement-tile {
width: 100%;
}
.edit-password-form__wrapper
> .announcement-tile
.announcement-content-subject {
margin-bottom: 2px;
}
#edit-new-password + div > button > svg,
#edit-confirm-password + div > button > svg,
#edit-current-password + div > button > svg {
color: var(--env-var-color-29);
}
.edit-password-form__wrapper .announcement-tile {
margin: 0;
}
.edit-password-form__wrapper .announcement-tile .announcement-close {
margin-left: auto;
}
.MuiBox-root > .announcement-tile {
border-color: var(--env-var-color-27);
background-color: var(--lighter-env-var-color-27);
}
.MuiBox-root > .announcement-tile .announcement-content-body {
color: var(--env-var-color-28);
}
.MuiBox-root > .announcement-tile .announcement-close svg,
.edit-password-form__wrapper > .announcement-tile .announcement-close svg {
display: none;
}
#org-name-flex-container .Mui-disabled {
color: var(--env-var-color-2) !important;
}
+27 -2
View File
@@ -15,6 +15,7 @@ import {
Tabs,
Typography,
Stack,
Box,
} from "@mui/material";
import { ColoredLabel, StatusLabel } from "../../Components/Label/";
import Avatar from "../../Components/Avatar/";
@@ -29,7 +30,7 @@ import AddIcon from "@mui/icons-material/Add";
import Divider from "@mui/material/Divider";
import UploadIcon from "@mui/icons-material/Upload";
import SendIcon from "@mui/icons-material/Send";
import ImageIcon from '@mui/icons-material/Image';
import ImageIcon from "@mui/icons-material/Image";
// Redux
import { useSelector } from "react-redux";
@@ -38,6 +39,7 @@ import { getMonitors } from "../../Features/Monitors/monitorsSlice";
import { getMonitorsByUserId } from "../../Features/Monitors/monitorsSlice";
import ImageField from "../../Components/TextFields/Image";
import ProgressUpload from "../../Components/ProgressBars";
import Alert from "../../Components/Alert";
const cols = [
{
@@ -423,7 +425,30 @@ const Demo = () => {
</Stack>
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
<Stack justifyContent="center" alignItems="center">
<ProgressUpload icon={<ImageIcon />} label="image.jpg" size="2 MB"/>
<ProgressUpload icon={<ImageIcon />} label="image.jpg" size="2 MB" />
</Stack>
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
<Stack justifyContent="center" alignItems="center">
<Box width="500px">
<Alert
variant="info"
title="SSO login"
body="Since you logged in via SSO, you cannot reset or modify your password."
/>
</Box>
<Box width="500px" mt="5px">
<Alert
variant="warning"
body="New password must contain at least 8 characters and must have at least one uppercase letter, one number and one symbol."
/>
</Box>
<Box width="500px" mt="5px">
<Alert
variant="error"
title="Error"
body="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
/>
</Box>
</Stack>
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
</div>
+2 -2
View File
@@ -41,13 +41,13 @@
#login-email-input {
width: 308px;
height: 11px;
height: 1px;
font-size: var(--env-var-font-size-medium);
}
#login-password-input {
width: 308px;
height: 11px;
height: 1px;
font-size: var(--env-var-font-size-medium);
}
+21 -3
View File
@@ -88,14 +88,32 @@ const theme = createTheme({
},
content: {
pX: "80px",
pY: "40px"
pY: "40px",
},
gap: {
small: "8px",
medium: "12px",
ml: "16px",
large: "24px",
xl: "40px"
}
xl: "40px",
},
alert: {
info: {
color: secondaryMain,
bg: otherColorsWhite,
border: "#d0d5dd",
},
error: {
color: "#d92d20",
bg: "hsla(0, 100%, 52%, 0.03)",
border: "#f04438",
},
warning: {
color: "#f79009",
bg: "#fffaef",
border: "#fecf60"
},
},
});
export default theme;
+82
View File
@@ -7,6 +7,7 @@ const {
recoveryValidation,
recoveryTokenValidation,
newPasswordValidation,
deleteUserParamValidation
} = require("../validation/joi");
const logger = require("../utils/logger");
require("dotenv").config();
@@ -261,6 +262,86 @@ const resetPasswordController = async (req, res, next) => {
next(error);
}
};
/**
* Deletes a user and all associated monitors, checks, and alerts.
*
* @param {Object} req - The request object.
* @param {Object} res - The response object.
* @param {Function} next - The next middleware function.
* @returns {Object} The response object with success status and message.
* @throws {Error} If user validation fails or user is not found in the database.
*/
const deleteUserController = async (req, res, next) => {
try {
const token = req.headers.authorization.split(' ')[1];
const decodedToken = jwt.decode(token)
const { _id, email } = decodedToken;
const decodedTokenCastedAsRequest = {
params: {
userId: _id
},
body: {
email
}
}
// Validate user
await deleteUserParamValidation.validateAsync(decodedTokenCastedAsRequest.body);
// Check if the user exists
const user = await req.db.getUserByEmail(decodedTokenCastedAsRequest);
if (!user) {
throw new Error(errorMessages.DB_USER_NOT_FOUND);
}
// 1. Find all the monitors associated with the user id
const monitors = await req.db.getMonitorsByUserId(decodedTokenCastedAsRequest);
if (monitors) {
// 2. Delete jobs associated with each monitor
for (const monitor of monitors) {
await req.jobQueue.deleteJob(monitor);
}
// 3. Delete all checks associated with each monitor
for (const monitor of monitors) {
await req.db.deleteChecks(monitor._id);
}
// 4. Delete all alerts associated with each monitor
for (const monitor of monitors) {
await req.db.deleteAlertByMonitorId(monitor._id);
}
// 5. Delete each monitor
await req.db.deleteMonitorsByUserId(user._id);
// 6. Delete the user by id
await req.db.deleteUser(decodedTokenCastedAsRequest);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_DELETE_USER,
});
} else {
return res.status(404).json({
success: false,
msg: errorMessages.MONITOR_GET_BY_USER_ID,
});
}
} catch (error) {
error.service = SERVICE_NAME;
next(error);
}
};
module.exports = {
registerController,
loginController,
@@ -269,4 +350,5 @@ module.exports = {
recoveryRequestController,
validateRecoveryTokenController,
resetPasswordController,
deleteUserController,
};
+46
View File
@@ -115,6 +115,28 @@ const updateUser = async (req, res) => {
}
};
/**
* Delete a user by ID
* @async
* @param {Express.Request} req
* @param {Express.Response} res
* @returns {Promise<UserModel>}
* @throws {Error}
*/
const deleteUser = async (req, res) => {
const userId = req.params.userId;
try {
const deletedUser = await UserModel.findByIdAndDelete(userId);
if (!deletedUser) {
throw new Error(errorMessages.DB_USER_NOT_FOUND);
}
return deletedUser;
} catch (error) {
throw error;
}
};
/**
* Request a recovery token
* @async
@@ -319,6 +341,21 @@ const deleteAllMonitors = async (req, res) => {
}
};
/**
* Delete all monitors associated with a user ID
* @async
* @param {string} userId - The ID of the user whose monitors are to be deleted.
* @returns {Promise} A promise that resolves when the operation is complete.
*/
const deleteMonitorsByUserId = async (userId) => {
try {
const result = await Monitor.deleteMany({ userId: userId });
return result;
} catch (error) {
throw error;
}
};
/**
* Edit a monitor by ID
* @async
@@ -520,6 +557,13 @@ const deleteAlert = async (alertId) => {
}
};
/**
* Deletes alerts by monitor ID.
*
* @param {string} monitorId - The ID of the monitor.
* @returns {Promise} A promise that resolves to the result of the delete operation.
* @throws {Error} If an error occurs while deleting the alerts.
*/
const deleteAlertByMonitorId = async (monitorId) => {
try {
const result = await Alert.deleteMany({ monitorId });
@@ -534,6 +578,7 @@ module.exports = {
insertUser,
getUserByEmail,
updateUser,
deleteUser,
requestRecoveryToken,
validateRecoveryToken,
resetPassword,
@@ -555,4 +600,5 @@ module.exports = {
editAlert,
deleteAlert,
deleteAlertByMonitorId,
deleteMonitorsByUserId
};
+119 -370
View File
File diff suppressed because it is too large. Load diff
+4
View File
@@ -1,7 +1,9 @@
const router = require("express").Router();
const { verifyJWT } = require("../middleware/verifyJWT");
const { verifyOwnership } = require("../middleware/verifyOwnership");
const multer = require("multer");
const upload = multer();
const User = require("../models/user");
const {
registerController,
@@ -11,6 +13,7 @@ const {
validateRecoveryTokenController,
resetPasswordController,
checkAdminController,
deleteUserController
} = require("../controllers/authController");
//Auth routes
@@ -23,6 +26,7 @@ router.post(
userEditController
);
router.get("/users/admin", checkAdminController);
router.delete("/user/:userId", verifyJWT, verifyOwnership(User, "userId") , deleteUserController);
//Recovery routes
router.post("/recovery/request", recoveryRequestController);
+1
View File
@@ -57,6 +57,7 @@ const successMessages = {
AUTH_VERIFY_RECOVERY_TOKEN: "Recovery token verified successfully",
AUTH_RESET_PASSWORD: "Password reset successfully",
AUTH_ADMIN_CHECK: "Admin check completed successfully",
AUTH_DELETE_USER: "User deleted successfully",
// Check Controller
CHECK_CREATE: "Check created successfully",
+5
View File
@@ -46,6 +46,10 @@ const newPasswordValidation = joi.object({
confirm: joi.string(),
});
const deleteUserParamValidation = joi.object({
email: joi.string().email().required(),
});
//****************************************
// Monitors
//****************************************
@@ -161,4 +165,5 @@ module.exports = {
createCheckBodyValidation,
getChecksParamValidation,
deleteChecksParamValidation,
deleteUserParamValidation
};