Merge branch 'develop' into 915-be-add-middeware-for-verification-of-refresh-token

This commit is contained in:
Rushi Gandhi
2024-10-17 12:01:43 +05:30
78 changed files with 10826 additions and 11692 deletions

View File

@@ -120,7 +120,7 @@
opacity: 0.4;
}
.monitors .MuiTable-root .MuiTableHead-root .MuiTableCell-root {
.table-container .MuiTable-root .MuiTableHead-root .MuiTableCell-root {
text-transform: uppercase;
opacity: 0.8;
font-size: var(--env-var-font-size-small-plus);

View File

@@ -0,0 +1,80 @@
import LoadingButton from "@mui/lab/LoadingButton";
import { Button, Modal, Stack, Typography } from "@mui/material";
import PropTypes from "prop-types";
const Dialog = ({ modelTitle, modelDescription,
open, onClose, title, confirmationBtnLbl, confirmationBtnOnClick, cancelBtnLbl, cancelBtnOnClick, theme, isLoading, description }) => {
return <Modal
aria-labelledby={modelTitle}
aria-describedby={modelDescription}
open={open}
onClose={onClose} >
<Stack
gap={theme.spacing(2)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
boxShadow: 24,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
>
<Typography id={modelTitle} component="h2"
fontSize={16}
color={theme.palette.text.primary}
fontWeight={600}>
{title}
</Typography>
{description && <Typography id={modelDescription} color={theme.palette.text.tertiary}>
{description}
</Typography>}
<Stack
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(12)}
justifyContent="flex-end"
>
<Button
variant="text"
color="info"
onClick={cancelBtnOnClick}
>
{cancelBtnLbl}
</Button>
<LoadingButton
variant="contained"
color="error"
loading= {isLoading}
onClick={confirmationBtnOnClick}
>
{confirmationBtnLbl}
</LoadingButton>
</Stack>
</Stack>
</Modal>
}
Dialog.propTypes = {
modelTitle: PropTypes.string.isRequired,
modelDescription: PropTypes.string.isRequired,
open: PropTypes.bool.isRequired,
onClose: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
confirmationBtnLbl: PropTypes.string.isRequired,
confirmationBtnOnClick: PropTypes.func.isRequired,
cancelBtnLbl: PropTypes.string.isRequired,
cancelBtnOnClick: PropTypes.func.isRequired,
theme: PropTypes.object.isRequired,
isLoading: PropTypes.bool.isRequired,
description: PropTypes.string
}
export default Dialog

View File

@@ -223,6 +223,7 @@ const TeamPanel = () => {
return (
<TabPanel
className="team-panel table-container"
value="team"
sx={{
"& h1": {

View File

@@ -61,7 +61,7 @@ const Incidents = () => {
};
return (
<Stack className="incidents" pt={theme.spacing(6)} gap={theme.spacing(12)}>
<Stack className="incidents table-container" pt={theme.spacing(6)} gap={theme.spacing(12)}>
{loading ? (
<SkeletonLayout />
) : (

View File

@@ -46,7 +46,7 @@ const Maintenance = ({ isAdmin }) => {
return (
<Box
className="maintenance"
className="maintenance table-container"
sx={{
':has(> [class*="fallback__"])': {
position: "relative",

View File

@@ -2,7 +2,7 @@ import { useNavigate, useParams } from "react-router";
import { useTheme } from "@emotion/react";
import { useDispatch, useSelector } from "react-redux";
import { useEffect, useState } from "react";
import { Box, Button, Modal, Stack, Tooltip, Typography } from "@mui/material";
import { Box, Stack, Tooltip, Typography } from "@mui/material";
import { monitorValidation } from "../../../Validation/validation";
import { createToast } from "../../../Utils/toastUtils";
import { logger } from "../../../Utils/Logger";
@@ -24,6 +24,7 @@ import PulseDot from "../../../Components/Animated/PulseDot";
import SkeletonLayout from "./skeleton";
import LoadingButton from "@mui/lab/LoadingButton";
import "./index.css";
import Dialog from "../../../Components/Dialog";
/**
* Parses a URL string and returns a URL object.
@@ -447,71 +448,21 @@ const Configure = () => {
</Stack>
</>
)}
<Modal
aria-labelledby="modal-delete-monitor"
aria-describedby="delete-monitor-confirmation"
<Dialog
modelTitle="modal-delete-monitor"
modelDescription="delete-monitor-confirmation"
open={isOpen}
onClose={() => setIsOpen(false)}
disablePortal
title="Do you really want to delete this monitor?"
confirmationBtnLbl="Delete"
confirmationBtnOnClick={handleRemove}
cancelBtnLbl="Cancel"
cancelBtnOnClick={() => setIsOpen(false)}
theme={theme}
isLoading={isLoading}
description="Once deleted, this monitor cannot be retrieved."
>
<Stack
gap={theme.spacing(2)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
boxShadow: 24,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
>
<Typography
id="modal-delete-monitor"
component="h2"
fontSize={16}
color={theme.palette.text.primary}
fontWeight={600}
>
Do you really want to delete this monitor?
</Typography>
<Typography
id="delete-monitor-confirmation"
color={theme.palette.text.tertiary}
>
Once deleted, this monitor cannot be retrieved.
</Typography>
<Stack
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(12)}
justifyContent="flex-end"
>
<Button
variant="text"
color="info"
onClick={() => setIsOpen(false)}
>
Cancel
</Button>
<LoadingButton
variant="contained"
color="error"
loading={isLoading}
onClick={handleRemove}
>
Delete
</LoadingButton>
</Stack>
</Stack>
</Modal>
</Dialog>
</Stack>
);
};

View File

@@ -1,22 +1,19 @@
import PropTypes from "prop-types";
import { useEffect, useState, useCallback } from "react";
import {
Box,
Button,
Popover,
Stack,
Tooltip,
Typography,
useTheme,
Box,
Button,
Popover,
Stack,
Tooltip,
Typography,
useTheme,
} from "@mui/material";
import { useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { networkService } from "../../../main";
import { logger } from "../../../Utils/Logger";
import {
formatDurationRounded,
formatDurationSplit,
} from "../../../Utils/timeUtils";
import { formatDurationRounded, formatDurationSplit } from "../../../Utils/timeUtils";
import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart";
import ButtonGroup from "@mui/material/ButtonGroup";
import SettingsIcon from "../../../assets/icons/settings-bold.svg?react";
@@ -41,447 +38,455 @@ import { formatDateWithTz } from "../../../Utils/timeUtils";
* @component
*/
const DetailsPage = ({ isAdmin }) => {
const theme = useTheme();
const { statusColor, statusStyles, statusMsg, determineState } = useUtils();
const [monitor, setMonitor] = useState({});
const { monitorId } = useParams();
const { authToken } = useSelector((state) => state.auth);
const [dateRange, setDateRange] = useState("day");
const [certificateExpiry, setCertificateExpiry] = useState("N/A");
const navigate = useNavigate();
const theme = useTheme();
const { statusColor, statusStyles, statusMsg, determineState } = useUtils();
const [monitor, setMonitor] = useState({});
const { monitorId } = useParams();
const { authToken } = useSelector((state) => state.auth);
const [dateRange, setDateRange] = useState("day");
const [certificateExpiry, setCertificateExpiry] = useState("N/A");
const navigate = useNavigate();
const [anchorEl, setAnchorEl] = useState(null);
const openCertificate = (event) => {
setAnchorEl(event.currentTarget);
};
const closeCertificate = () => {
setAnchorEl(null);
};
const [anchorEl, setAnchorEl] = useState(null);
const openCertificate = (event) => {
setAnchorEl(event.currentTarget);
};
const closeCertificate = () => {
setAnchorEl(null);
};
const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D";
const uiTimezone = useSelector((state) => state.ui.timezone);
const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D";
const uiTimezone = useSelector((state) => state.ui.timezone);
const fetchMonitor = useCallback(async () => {
try {
const res = await networkService.getStatsByMonitorId({
authToken: authToken,
monitorId: monitorId,
sortOrder: null,
limit: null,
dateRange: dateRange,
numToDisplay: 50,
normalize: true,
});
setMonitor(res?.data?.data ?? {});
} catch (error) {
logger.error(error);
navigate("/not-found", { replace: true });
}
}, [authToken, monitorId, navigate, dateRange]);
const fetchMonitor = useCallback(async () => {
try {
const res = await networkService.getStatsByMonitorId({
authToken: authToken,
monitorId: monitorId,
sortOrder: null,
limit: null,
dateRange: dateRange,
numToDisplay: 50,
normalize: true,
});
setMonitor(res?.data?.data ?? {});
} catch (error) {
logger.error(error);
navigate("/not-found", { replace: true });
}
}, [authToken, monitorId, navigate, dateRange]);
useEffect(() => {
fetchMonitor();
}, [fetchMonitor]);
useEffect(() => {
fetchMonitor();
}, [fetchMonitor]);
useEffect(() => {
const fetchCertificate = async () => {
if (monitor?.type !== "http") {
return;
}
try {
const res = await networkService.getCertificateExpiry({
authToken: authToken,
monitorId: monitorId,
});
useEffect(() => {
const fetchCertificate = async () => {
if (monitor?.type !== "http") {
return;
}
try {
const res = await networkService.getCertificateExpiry({
authToken: authToken,
monitorId: monitorId,
});
if (res?.data?.data?.certificateDate) {
const date = res.data.data.certificateDate;
setCertificateExpiry(formatDateWithTz(date, dateFormat, uiTimezone) ?? "N/A");
}
} catch (error) {
console.error(error);
}
};
fetchCertificate();
}, [authToken, monitorId, monitor]);
if (res?.data?.data?.certificateDate) {
let [month, day, year] = res.data.data.certificateDate.split("/");
const date = new Date(year, month - 1, day);
const splitDuration = (duration) => {
const { time, format } = formatDurationSplit(duration);
return (
<>
{time}
<Typography component="span">{format}</Typography>
</>
);
};
setCertificateExpiry(
formatDateWithTz(date, dateFormat, uiTimezone) ?? "N/A"
);
}
} catch (error) {
console.error(error);
}
};
fetchCertificate();
}, [authToken, monitorId, monitor]);
let loading = Object.keys(monitor).length === 0;
const splitDuration = (duration) => {
const { time, format } = formatDurationSplit(duration);
return (
<>
{time}
<Typography component="span">{format}</Typography>
</>
);
};
const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
let loading = Object.keys(monitor).length === 0;
const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
return (
<Box className="monitor-details">
{loading ? (
<SkeletonLayout />
) : (
<>
<Breadcrumbs
list={[
{ name: "monitors", path: "/monitors" },
{ name: "details", path: `/monitors/${monitorId}` },
]}
/>
<Stack gap={theme.spacing(10)} mt={theme.spacing(10)}>
<Stack direction="row" gap={theme.spacing(2)}>
<Box>
<Typography component="h1" variant="h1">
{monitor.name}
</Typography>
<Stack
direction="row"
alignItems="center"
height="fit-content"
gap={theme.spacing(2)}
>
<Tooltip
title={statusMsg[determineState(monitor)]}
disableInteractive
slotProps={{
popper: {
modifiers: [
{
name: "offset",
options: {
offset: [0, -8],
},
},
],
},
}}
>
<Box>
<PulseDot color={statusColor[determineState(monitor)]} />
</Box>
</Tooltip>
<Typography component="h2" variant="h2">
{monitor.url?.replace(/^https?:\/\//, "") || "..."}
</Typography>
<Typography
position="relative"
variant="body2"
mt={theme.spacing(1)}
ml={theme.spacing(6)}
sx={{
"&:before": {
position: "absolute",
content: `""`,
width: 4,
height: 4,
borderRadius: "50%",
backgroundColor: theme.palette.text.tertiary,
opacity: 0.8,
left: -9,
top: "50%",
transform: "translateY(-50%)",
},
}}
>
Checking every {formatDurationRounded(monitor?.interval)}.
</Typography>
</Stack>
</Box>
<Stack
direction="row"
height={34}
sx={{
ml: "auto",
alignSelf: "flex-end",
}}
>
<IconBox
mr={theme.spacing(4)}
onClick={openCertificate}
sx={{
cursor: "pointer",
"& svg": {
width: 23,
height: 23,
top: "52%",
},
}}
>
<CertificateIcon />
</IconBox>
<Popover
id="certificate-dropdown"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={closeCertificate}
disableScrollLock
marginThreshold={null}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
slotProps={{
paper: {
sx: {
mt: theme.spacing(4),
py: theme.spacing(2),
px: theme.spacing(4),
width: 140,
backgroundColor: theme.palette.background.accent,
},
},
}}
>
<Typography variant="body2">Certificate Expiry</Typography>
<Typography
component="span"
fontSize={13}
color={theme.palette.text.primary}
>
{certificateExpiry}
</Typography>
</Popover>
{isAdmin && (
<Button
variant="contained"
color="secondary"
onClick={() => navigate(`/monitors/configure/${monitorId}`)}
sx={{
px: theme.spacing(5),
"& svg": {
mr: theme.spacing(3),
"& path": {
stroke: theme.palette.text.tertiary,
},
},
}}
>
<SettingsIcon /> Configure
</Button>
)}
</Stack>
</Stack>
<Stack direction="row" gap={theme.spacing(8)}>
<StatBox sx={statusStyles[determineState(monitor)]}>
<Typography component="h2">active for</Typography>
<Typography>
{splitDuration(monitor?.uptimeDuration)}
</Typography>
</StatBox>
<StatBox>
<Typography component="h2">last check</Typography>
<Typography>
{splitDuration(monitor?.lastChecked)}
<Typography component="span">ago</Typography>
</Typography>
</StatBox>
<StatBox>
<Typography component="h2">last response time</Typography>
<Typography>
{monitor?.latestResponseTime}
<Typography component="span">ms</Typography>
</Typography>
</StatBox>
</Stack>
<Box>
<Stack
direction="row"
justifyContent="space-between"
alignItems="flex-end"
gap={theme.spacing(4)}
mb={theme.spacing(8)}
>
<Typography variant="body2">
Showing statistics for past{" "}
{dateRange === "day"
? "24 hours"
: dateRange === "week"
? "7 days"
: "30 days"}
.
</Typography>
<ButtonGroup sx={{ height: 32 }}>
<Button
variant="group"
filled={(dateRange === "day").toString()}
onClick={() => setDateRange("day")}
>
Day
</Button>
<Button
variant="group"
filled={(dateRange === "week").toString()}
onClick={() => setDateRange("week")}
>
Week
</Button>
<Button
variant="group"
filled={(dateRange === "month").toString()}
onClick={() => setDateRange("month")}
>
Month
</Button>
</ButtonGroup>
</Stack>
<Stack direction="row" flexWrap="wrap" gap={theme.spacing(8)}>
<ChartBox>
<Stack>
<IconBox>
<UptimeIcon />
</IconBox>
<Typography component="h2">Uptime</Typography>
</Stack>
<Stack justifyContent="space-between">
<Box position="relative">
<Typography>Total Checks</Typography>
<Typography component="span">
{hoveredUptimeData !== null
? hoveredUptimeData.totalChecks
: monitor?.periodTotalChecks}
</Typography>
{hoveredUptimeData !== null &&
hoveredUptimeData.time !== null && (
<Typography
component="h5"
position="absolute"
top="100%"
fontSize={11}
color={theme.palette.text.tertiary}
>
{formatDateWithTz(
hoveredUptimeData.time,
dateFormat,
uiTimezone
)}
</Typography>
)}
</Box>
<Box>
<Typography>Uptime Percentage</Typography>
<Typography component="span">
{hoveredUptimeData !== null
? Math.floor(
hoveredUptimeData.uptimePercentage * 10
) / 10
: Math.floor(monitor?.periodUptime * 10) / 10}
<Typography component="span">%</Typography>
</Typography>
</Box>
</Stack>
<UpBarChart
data={monitor?.aggregateData}
type={dateRange}
onBarHover={setHoveredUptimeData}
/>
</ChartBox>
<ChartBox>
<Stack>
<IconBox>
<IncidentsIcon />
</IconBox>
<Typography component="h2">Incidents</Typography>
</Stack>
<Box position="relative">
<Typography>Total Incidents</Typography>
<Typography component="span">
{hoveredIncidentsData !== null
? hoveredIncidentsData.totalIncidents
: monitor?.periodIncidents}
</Typography>
{hoveredIncidentsData !== null &&
hoveredIncidentsData.time !== null && (
<Typography
component="h5"
position="absolute"
top="100%"
fontSize={11}
color={theme.palette.text.tertiary}
>
{formatDateWithTz(
hoveredIncidentsData.time,
dateFormat,
uiTimezone
)}
</Typography>
)}
</Box>
<DownBarChart
data={monitor?.aggregateData}
type={dateRange}
onBarHover={setHoveredIncidentsData}
/>
</ChartBox>
<ChartBox justifyContent="space-between">
<Stack>
<IconBox>
<AverageResponseIcon />
</IconBox>
<Typography component="h2">
Average Response Time
</Typography>
</Stack>
<ResponseGaugeChart
data={[{ response: monitor?.periodAvgResponseTime }]}
/>
</ChartBox>
<ChartBox sx={{ padding: 0 }}>
<Stack pt={theme.spacing(8)} pl={theme.spacing(8)}>
<IconBox>
<ResponseTimeIcon />
</IconBox>
<Typography component="h2">Response Times</Typography>
</Stack>
<MonitorDetailsAreaChart
checks={[...monitor.checks].reverse()}
/>
</ChartBox>
<ChartBox
gap={theme.spacing(8)}
sx={{
flex: "100%",
height: "fit-content",
"& nav": { mt: theme.spacing(12) },
}}
>
<Stack mb={theme.spacing(8)}>
<IconBox>
<HistoryIcon />
</IconBox>
<Typography
component="h2"
color={theme.palette.text.secondary}
>
History
</Typography>
</Stack>
<PaginationTable
monitorId={monitorId}
dateRange={dateRange}
/>
</ChartBox>
</Stack>
</Box>
</Stack>
</>
)}
</Box>
);
return (
<Box className="monitor-details">
{loading ? (
<SkeletonLayout />
) : (
<>
<Breadcrumbs
list={[
{ name: "monitors", path: "/monitors" },
{ name: "details", path: `/monitors/${monitorId}` },
]}
/>
<Stack
gap={theme.spacing(10)}
mt={theme.spacing(10)}
>
<Stack
direction="row"
gap={theme.spacing(2)}
>
<Box>
<Typography
component="h1"
variant="h1"
>
{monitor.name}
</Typography>
<Stack
direction="row"
alignItems="center"
height="fit-content"
gap={theme.spacing(2)}
>
<Tooltip
title={statusMsg[determineState(monitor)]}
disableInteractive
slotProps={{
popper: {
modifiers: [
{
name: "offset",
options: {
offset: [0, -8],
},
},
],
},
}}
>
<Box>
<PulseDot color={statusColor[determineState(monitor)]} />
</Box>
</Tooltip>
<Typography
component="h2"
variant="h2"
>
{monitor.url?.replace(/^https?:\/\//, "") || "..."}
</Typography>
<Typography
position="relative"
variant="body2"
mt={theme.spacing(1)}
ml={theme.spacing(6)}
sx={{
"&:before": {
position: "absolute",
content: `""`,
width: 4,
height: 4,
borderRadius: "50%",
backgroundColor: theme.palette.text.tertiary,
opacity: 0.8,
left: -9,
top: "50%",
transform: "translateY(-50%)",
},
}}
>
Checking every {formatDurationRounded(monitor?.interval)}.
</Typography>
</Stack>
</Box>
<Stack
direction="row"
height={34}
sx={{
ml: "auto",
alignSelf: "flex-end",
}}
>
<IconBox
mr={theme.spacing(4)}
onClick={openCertificate}
sx={{
cursor: "pointer",
"& svg": {
width: 23,
height: 23,
top: "52%",
},
}}
>
<CertificateIcon />
</IconBox>
<Popover
id="certificate-dropdown"
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={closeCertificate}
disableScrollLock
marginThreshold={null}
anchorOrigin={{
vertical: "bottom",
horizontal: "center",
}}
transformOrigin={{
vertical: "top",
horizontal: "center",
}}
slotProps={{
paper: {
sx: {
mt: theme.spacing(4),
py: theme.spacing(2),
px: theme.spacing(4),
width: 140,
backgroundColor: theme.palette.background.accent,
},
},
}}
>
<Typography variant="body2">Certificate Expiry</Typography>
<Typography
component="span"
fontSize={13}
color={theme.palette.text.primary}
>
{certificateExpiry}
</Typography>
</Popover>
{isAdmin && (
<Button
variant="contained"
color="secondary"
onClick={() => navigate(`/monitors/configure/${monitorId}`)}
sx={{
px: theme.spacing(5),
"& svg": {
mr: theme.spacing(3),
"& path": {
stroke: theme.palette.text.tertiary,
},
},
}}
>
<SettingsIcon /> Configure
</Button>
)}
</Stack>
</Stack>
<Stack
direction="row"
gap={theme.spacing(8)}
>
<StatBox sx={statusStyles[determineState(monitor)]}>
<Typography component="h2">active for</Typography>
<Typography>{splitDuration(monitor?.uptimeDuration)}</Typography>
</StatBox>
<StatBox>
<Typography component="h2">last check</Typography>
<Typography>
{splitDuration(monitor?.lastChecked)}
<Typography component="span">ago</Typography>
</Typography>
</StatBox>
<StatBox>
<Typography component="h2">last response time</Typography>
<Typography>
{monitor?.latestResponseTime}
<Typography component="span">ms</Typography>
</Typography>
</StatBox>
</Stack>
<Box>
<Stack
direction="row"
justifyContent="space-between"
alignItems="flex-end"
gap={theme.spacing(4)}
mb={theme.spacing(8)}
>
<Typography variant="body2">
Showing statistics for past{" "}
{dateRange === "day"
? "24 hours"
: dateRange === "week"
? "7 days"
: "30 days"}
.
</Typography>
<ButtonGroup sx={{ height: 32 }}>
<Button
variant="group"
filled={(dateRange === "day").toString()}
onClick={() => setDateRange("day")}
>
Day
</Button>
<Button
variant="group"
filled={(dateRange === "week").toString()}
onClick={() => setDateRange("week")}
>
Week
</Button>
<Button
variant="group"
filled={(dateRange === "month").toString()}
onClick={() => setDateRange("month")}
>
Month
</Button>
</ButtonGroup>
</Stack>
<Stack
direction="row"
flexWrap="wrap"
gap={theme.spacing(8)}
>
<ChartBox>
<Stack>
<IconBox>
<UptimeIcon />
</IconBox>
<Typography component="h2">Uptime</Typography>
</Stack>
<Stack justifyContent="space-between">
<Box position="relative">
<Typography>Total Checks</Typography>
<Typography component="span">
{hoveredUptimeData !== null
? hoveredUptimeData.totalChecks
: monitor?.periodTotalChecks}
</Typography>
{hoveredUptimeData !== null && hoveredUptimeData.time !== null && (
<Typography
component="h5"
position="absolute"
top="100%"
fontSize={11}
color={theme.palette.text.tertiary}
>
{formatDateWithTz(
hoveredUptimeData.time,
dateFormat,
uiTimezone
)}
</Typography>
)}
</Box>
<Box>
<Typography>Uptime Percentage</Typography>
<Typography component="span">
{hoveredUptimeData !== null
? Math.floor(hoveredUptimeData.uptimePercentage * 10) / 10
: Math.floor(monitor?.periodUptime * 10) / 10}
<Typography component="span">%</Typography>
</Typography>
</Box>
</Stack>
<UpBarChart
data={monitor?.aggregateData}
type={dateRange}
onBarHover={setHoveredUptimeData}
/>
</ChartBox>
<ChartBox>
<Stack>
<IconBox>
<IncidentsIcon />
</IconBox>
<Typography component="h2">Incidents</Typography>
</Stack>
<Box position="relative">
<Typography>Total Incidents</Typography>
<Typography component="span">
{hoveredIncidentsData !== null
? hoveredIncidentsData.totalIncidents
: monitor?.periodIncidents}
</Typography>
{hoveredIncidentsData !== null &&
hoveredIncidentsData.time !== null && (
<Typography
component="h5"
position="absolute"
top="100%"
fontSize={11}
color={theme.palette.text.tertiary}
>
{formatDateWithTz(
hoveredIncidentsData.time,
dateFormat,
uiTimezone
)}
</Typography>
)}
</Box>
<DownBarChart
data={monitor?.aggregateData}
type={dateRange}
onBarHover={setHoveredIncidentsData}
/>
</ChartBox>
<ChartBox justifyContent="space-between">
<Stack>
<IconBox>
<AverageResponseIcon />
</IconBox>
<Typography component="h2">Average Response Time</Typography>
</Stack>
<ResponseGaugeChart
data={[{ response: monitor?.periodAvgResponseTime }]}
/>
</ChartBox>
<ChartBox sx={{ padding: 0 }}>
<Stack
pt={theme.spacing(8)}
pl={theme.spacing(8)}
>
<IconBox>
<ResponseTimeIcon />
</IconBox>
<Typography component="h2">Response Times</Typography>
</Stack>
<MonitorDetailsAreaChart checks={[...monitor.checks].reverse()} />
</ChartBox>
<ChartBox
gap={theme.spacing(8)}
sx={{
flex: "100%",
height: "fit-content",
"& nav": { mt: theme.spacing(12) },
}}
>
<Stack mb={theme.spacing(8)}>
<IconBox>
<HistoryIcon />
</IconBox>
<Typography
component="h2"
color={theme.palette.text.secondary}
>
History
</Typography>
</Stack>
<PaginationTable
monitorId={monitorId}
dateRange={dateRange}
/>
</ChartBox>
</Stack>
</Box>
</Stack>
</>
)}
</Box>
);
};
DetailsPage.propTypes = {
isAdmin: PropTypes.bool,
isAdmin: PropTypes.bool,
};
export default DetailsPage;

View File

@@ -5,13 +5,9 @@ import { useNavigate } from "react-router-dom";
import { createToast } from "../../../Utils/toastUtils";
import { logger } from "../../../Utils/Logger";
import {
Button,
IconButton,
Menu,
MenuItem,
Modal,
Stack,
Typography,
MenuItem
} from "@mui/material";
import {
deleteUptimeMonitor,
@@ -20,6 +16,7 @@ import {
} from "../../../Features/UptimeMonitors/uptimeMonitorsSlice";
import Settings from "../../../assets/icons/settings-bold.svg?react";
import PropTypes from "prop-types";
import Dialog from "../../../Components/Dialog"
const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => {
const [anchorEl, setAnchorEl] = useState(null);
@@ -29,7 +26,7 @@ const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => {
const theme = useTheme();
const authState = useSelector((state) => state.auth);
const authToken = authState.authToken;
const { isLoading } = useSelector((state) => state.uptimeMonitors);
const handleRemove = async (event) => {
event.preventDefault();
@@ -199,69 +196,21 @@ const ActionsMenu = ({ monitor, isAdmin, updateCallback }) => {
</MenuItem>
)}
</Menu>
<Modal
aria-labelledby="modal-delete-monitor"
aria-describedby="delete-monitor-confirmation"
<Dialog
modelTitle="modal-delete-monitor"
modelDescription="delete-monitor-confirmation"
open={isOpen}
onClose={(e) => {
e.stopPropagation();
setIsOpen(false);
}}
onClose={() => setIsOpen(false)}
title="Do you really want to delete this monitor?"
confirmationBtnLbl="Delete"
confirmationBtnOnClick={e => { e.stopPropagation(); handleRemove(e) }}
cancelBtnLbl="Cancel"
cancelBtnOnClick={e => { e.stopPropagation(); setIsOpen(false) }}
theme={theme}
isLoading={isLoading}
description="Once deleted, this monitor cannot be retrieved."
>
<Stack
gap={theme.spacing(2)}
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: theme.palette.background.main,
border: 1,
borderColor: theme.palette.border.light,
borderRadius: theme.shape.borderRadius,
boxShadow: 24,
p: theme.spacing(15),
"&:focus": {
outline: "none",
},
}}
>
<Typography id="modal-delete-monitor" component="h2" variant="h2">
Do you really want to delete this monitor?
</Typography>
<Typography id="delete-monitor-confirmation" variant="body1">
Once deleted, this monitor cannot be retrieved.
</Typography>
<Stack
direction="row"
gap={theme.spacing(4)}
mt={theme.spacing(12)}
justifyContent="flex-end"
>
<Button
variant="text"
color="info"
onClick={(e) => {
e.stopPropagation();
setIsOpen(false);
}}
>
Cancel
</Button>
<Button
variant="contained"
color="error"
onClick={(e) => {
e.stopPropagation(e);
handleRemove(e);
}}
>
Delete
</Button>
</Stack>
</Stack>
</Modal>
</Dialog>
</>
);
};

View File

@@ -45,7 +45,7 @@ const Monitors = ({ isAdmin }) => {
monitorState?.monitorsSummary?.monitors?.length === 0;
return (
<Stack className="monitors" gap={theme.spacing(8)}>
<Stack className="monitors table-container" gap={theme.spacing(8)}>
{loading ? (
<SkeletonLayout />
) : (

View File

@@ -22,6 +22,7 @@ import { ConfigBox } from "./styled";
import { networkService } from "../../main";
import { settingsValidation } from "../../Validation/validation";
import { useNavigate } from "react-router";
import Dialog from "../../Components/Dialog"
const SECONDS_PER_DAY = 86400;
@@ -39,6 +40,8 @@ const Settings = ({ isAdmin }) => {
});
const [version,setVersion]=useState("unknown");
const [errors, setErrors] = useState({});
const deleteStatsMonitorsInitState = { deleteMonitors: false, deleteStats: false };
const [isOpen, setIsOpen] = useState(deleteStatsMonitorsInitState);
const dispatch = useDispatch();
const navigate = useNavigate();
@@ -139,6 +142,8 @@ const Settings = ({ isAdmin }) => {
} catch (error) {
logger.error(error);
createToast({ body: "Failed to clear stats" });
} finally {
setIsOpen(deleteStatsMonitorsInitState)
}
};
@@ -167,9 +172,11 @@ const Settings = ({ isAdmin }) => {
} catch (error) {
logger.error(error);
createToast({ Body: "Failed to delete all monitors" });
} finally {
setIsOpen(deleteStatsMonitorsInitState)
}
};
return (
<Box
className="settings"
@@ -243,17 +250,30 @@ const Settings = ({ isAdmin }) => {
/>
<Box>
<Typography>Clear all stats. This is irreversible.</Typography>
<LoadingButton
<Button
variant="contained"
color="error"
loading={isLoading || authIsLoading || checksIsLoading}
onClick={handleClearStats}
color="error"
onClick={()=>setIsOpen({...deleteStatsMonitorsInitState, deleteStats: true})}
sx={{ mt: theme.spacing(4) }}
>
Clear all stats
</LoadingButton>
</Button>
</Box>
</Stack>
<Dialog
modelTitle="model-clear-stats"
modelDescription="clear-stats-confirmation"
open={isOpen.deleteStats}
onClose={() => setIsOpen(deleteStatsMonitorsInitState)}
title="Do you want to clear all stats?"
confirmationBtnLbl="Yes, clear all stats"
confirmationBtnOnClick={handleClearStats}
cancelBtnLbl="Cancel"
cancelBtnOnClick={() => setIsOpen(deleteStatsMonitorsInitState)}
theme={theme}
isLoading={isLoading || authIsLoading || checksIsLoading}
>
</Dialog>
</ConfigBox>
)}
{isAdmin && (
@@ -283,13 +303,27 @@ const Settings = ({ isAdmin }) => {
variant="contained"
color="error"
loading={isLoading || authIsLoading || checksIsLoading}
onClick={handleDeleteAllMonitors}
onClick={()=>setIsOpen({...deleteStatsMonitorsInitState, deleteMonitors: true})}
sx={{ mt: theme.spacing(4) }}
>
Remove all monitors
</LoadingButton>
</Box>
</Stack>
<Dialog
modelTitle="model-delete-all-monitors"
modelDescription="delete-all-monitors-confirmation"
open={isOpen.deleteMonitors}
onClose={() => setIsOpen(deleteStatsMonitorsInitState)}
title="Do you want to remove all monitors?"
confirmationBtnLbl="Yes, clear all monitors"
confirmationBtnOnClick={handleDeleteAllMonitors}
cancelBtnLbl="Cancel"
cancelBtnOnClick={() => setIsOpen(deleteStatsMonitorsInitState)}
theme={theme}
isLoading={isLoading || authIsLoading || checksIsLoading}
>
</Dialog>
</ConfigBox>
)}
{isAdmin && (

8
Server/.mocharc.cjs Normal file
View File

@@ -0,0 +1,8 @@
module.exports = {
require: ["esm", "chai/register-expect.js"], // Include Chai's "expect" interface globally
spec: "tests/**/*.test.js", // Specify test files
timeout: 5000, // Set test-case timeout in milliseconds
recursive: true, // Include subdirectories
reporter: "spec", // Use the "spec" reporter
exit: true, // Force Mocha to quit after tests complete
};

View File

@@ -1,8 +0,0 @@
module.exports = {
require: ["chai/register-expect.js"], // Include Chai's "expect" interface globally
spec: "tests/**/*.test.js", // Specify test files
timeout: 5000, // Set test-case timeout in milliseconds
recursive: true, // Include subdirectories
reporter: "spec", // Use the "spec" reporter
exit: true, // Force Mocha to quit after tests complete
};

View File

@@ -1,8 +1,8 @@
{
"all": true,
"include": ["controllers/*.js"],
"exclude": ["**/*.test.js"],
"reporter": ["html", "text", "lcov"],
"sourceMap": false,
"instrument": true
"all": true,
"include": ["controllers/*.js", "utils/*.js"],
"exclude": ["**/*.test.js"],
"reporter": ["html", "text", "lcov"],
"sourceMap": false,
"instrument": true
}

View File

@@ -12,4 +12,4 @@ const connectDbAndRunServer = async (app, db) => {
}
};
module.exports = { connectDbAndRunServer };
export { connectDbAndRunServer };

View File

@@ -1,20 +1,21 @@
const {
registrationBodyValidation,
loginValidation,
editUserParamValidation,
editUserBodyValidation,
recoveryValidation,
recoveryTokenValidation,
newPasswordValidation,
} = require("../validation/joi");
const logger = require("../utils/logger");
require("dotenv").config();
const { errorMessages, successMessages } = require("../utils/messages");
const jwt = require("jsonwebtoken");
const SERVICE_NAME = "AuthController";
const { getTokenFromHeaders, tokenType } = require("../utils/utils");
const crypto = require("crypto");
const { handleValidationError, handleError } = require("./controllerUtils");
import {
registrationBodyValidation,
loginValidation,
editUserParamValidation,
editUserBodyValidation,
recoveryValidation,
recoveryTokenValidation,
newPasswordValidation,
} from "../validation/joi.js";
import logger from "../utils/logger.js";
import dotenv from "dotenv";
import { errorMessages, successMessages } from "../utils/messages.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders, tokenType } from "../utils/utils.js";
import crypto from "crypto";
import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "authController";
dotenv.config();
/**
* Creates and returns JWT token with an arbitrary payload
@@ -26,15 +27,21 @@ const { handleValidationError, handleError } = require("./controllerUtils");
* @throws {Error}
*/
const issueToken = (payload, typeOfToken, appSettings) => {
try {
const tokenTTL = (typeOfToken === tokenType.REFRESH_TOKEN) ? (appSettings?.refreshTokenTTL?? "7d") : (appSettings?.jwtTTL?? "2h");
const tokenSecret = (typeOfToken === tokenType.REFRESH_TOKEN) ? appSettings?.refreshTokenSecret : appSettings?.jwtSecret;
const payloadData = (typeOfToken === tokenType.REFRESH_TOKEN) ? {} : payload;
try {
const tokenTTL =
typeOfToken === tokenType.REFRESH_TOKEN
? (appSettings?.refreshTokenTTL ?? "7d")
: (appSettings?.jwtTTL ?? "2h");
const tokenSecret =
typeOfToken === tokenType.REFRESH_TOKEN
? appSettings?.refreshTokenSecret
: appSettings?.jwtSecret;
const payloadData = typeOfToken === tokenType.REFRESH_TOKEN ? {} : payload;
return jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL });
} catch (error) {
throw handleError(error, SERVICE_NAME, "issueToken");
}
return jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL });
} catch (error) {
throw handleError(error, SERVICE_NAME, "issueToken");
}
};
/**
@@ -50,66 +57,65 @@ const issueToken = (payload, typeOfToken, appSettings) => {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
const registerUser = async (req, res, next) => {
// joi validation
try {
await registrationBodyValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
// Create a new user
try {
const { inviteToken } = req.body;
// If superAdmin exists, a token should be attached to all further register requests
const superAdminExists = await req.db.checkSuperadmin(req, res);
if (superAdminExists) {
await req.db.getInviteTokenAndDelete(inviteToken);
} else {
// This is the first account, create JWT secret to use if one is not supplied by env
const jwtSecret = crypto.randomBytes(64).toString("hex");
await req.db.updateAppSettings({ jwtSecret });
}
// joi validation
try {
await registrationBodyValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
// Create a new user
try {
const { inviteToken } = req.body;
// If superAdmin exists, a token should be attached to all further register requests
const superAdminExists = await req.db.checkSuperadmin(req, res);
if (superAdminExists) {
await req.db.getInviteTokenAndDelete(inviteToken);
} else {
// This is the first account, create JWT secret to use if one is not supplied by env
const jwtSecret = crypto.randomBytes(64).toString("hex");
await req.db.updateAppSettings({ jwtSecret });
}
const newUser = await req.db.insertUser({ ...req.body }, req.file);
const newUser = await req.db.insertUser({ ...req.body }, req.file);
logger.info(successMessages.AUTH_CREATE_USER, {
service: SERVICE_NAME,
userId: newUser._id,
});
logger.info(successMessages.AUTH_CREATE_USER, {
service: SERVICE_NAME,
userId: newUser._id,
});
const userForToken = { ...newUser._doc };
delete userForToken.profileImage;
delete userForToken.avatarImage;
const userForToken = { ...newUser._doc };
delete userForToken.profileImage;
delete userForToken.avatarImage;
const appSettings = await req.settingsService.getSettings();
const appSettings = await req.settingsService.getSettings();
const token = issueToken(userForToken, tokenType.ACCESS_TOKEN, appSettings);
const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings);
const token = issueToken(userForToken, tokenType.ACCESS_TOKEN, appSettings);
const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings);
req.emailService
.buildAndSendEmail(
"welcomeEmailTemplate",
{ name: newUser.firstName },
newUser.email,
"Welcome to Uptime Monitor"
)
.catch((error) => {
logger.error("Error sending welcome email", {
service: SERVICE_NAME,
error: error.message,
});
});
req.emailService
.buildAndSendEmail(
"welcomeEmailTemplate",
{ name: newUser.firstName },
newUser.email,
"Welcome to Uptime Monitor"
)
.catch((error) => {
logger.error("Error sending welcome email", {
service: SERVICE_NAME,
error: error.message,
});
});
return res.status(200).json({
success: true,
msg: successMessages.AUTH_CREATE_USER,
data: { user: newUser, token: token, refreshToken: refreshToken },
});
} catch (error) {
console.log("ERROR", error);
next(handleError(error, SERVICE_NAME, "registerController"));
}
return res.status(200).json({
success: true,
msg: successMessages.AUTH_CREATE_USER,
data: { user: newUser, token: token, refreshToken: refreshToken },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "registerController"));
}
};
/**
@@ -125,45 +131,46 @@ const registerUser = async (req, res, next) => {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422) or the password is incorrect.
*/
const loginUser = async (req, res, next) => {
try {
await loginValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
try {
const { email, password } = req.body;
// Check if user exists
const user = await req.db.getUserByEmail(email);
try {
await loginValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
try {
const { email, password } = req.body;
// Compare password
const match = await user.comparePassword(password);
if (match !== true) {
next(new Error(errorMessages.AUTH_INCORRECT_PASSWORD));
return;
}
// Check if user exists
const user = await req.db.getUserByEmail(email);
// Remove password from user object. Should this be abstracted to DB layer?
const userWithoutPassword = { ...user._doc };
delete userWithoutPassword.password;
delete userWithoutPassword.avatarImage;
// Compare password
const match = await user.comparePassword(password);
if (match !== true) {
next(new Error(errorMessages.AUTH_INCORRECT_PASSWORD));
return;
}
// Happy path, return token
const appSettings = await req.settingsService.getSettings();
const token = issueToken(userWithoutPassword, tokenType.ACCESS_TOKEN, appSettings);
const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings);
// reset avatar image
userWithoutPassword.avatarImage = user.avatarImage;
// Remove password from user object. Should this be abstracted to DB layer?
const userWithoutPassword = { ...user._doc };
delete userWithoutPassword.password;
delete userWithoutPassword.avatarImage;
return res.status(200).json({
success: true,
msg: successMessages.AUTH_LOGIN_USER,
data: { user: userWithoutPassword, token: token, refreshToken: refreshToken },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "loginController"));
}
// Happy path, return token
const appSettings = await req.settingsService.getSettings();
const token = issueToken(userWithoutPassword, tokenType.ACCESS_TOKEN, appSettings);
const refreshToken = issueToken({}, tokenType.REFRESH_TOKEN, appSettings);
// reset avatar image
userWithoutPassword.avatarImage = user.avatarImage;
return res.status(200).json({
success: true,
msg: successMessages.AUTH_LOGIN_USER,
data: { user: userWithoutPassword, token: token, refreshToken: refreshToken },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "loginUser"));
}
};
/**
@@ -181,58 +188,58 @@ const loginUser = async (req, res, next) => {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422), the user is unauthorized (401), or the password is incorrect (403).
*/
const editUser = async (req, res, next) => {
try {
await editUserParamValidation.validateAsync(req.params);
await editUserBodyValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
try {
await editUserParamValidation.validateAsync(req.params);
await editUserBodyValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
// TODO is this neccessary any longer? Verify ownership middleware should handle this
if (req.params.userId !== req.user._id.toString()) {
const error = new Error(errorMessages.AUTH_UNAUTHORIZED);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
return;
}
// TODO is this neccessary any longer? Verify ownership middleware should handle this
if (req.params.userId !== req.user._id.toString()) {
const error = new Error(errorMessages.AUTH_UNAUTHORIZED);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
return;
}
try {
// Change Password check
if (req.body.password && req.body.newPassword) {
// Get token from headers
const token = getTokenFromHeaders(req.headers);
// Get email from token
const { jwtSecret } = req.settingsService.getSettings();
const { email } = jwt.verify(token, jwtSecret);
// Add user email to body for DB operation
req.body.email = email;
// Get user
const user = await req.db.getUserByEmail(email);
// Compare passwords
const match = await user.comparePassword(req.body.password);
// If not a match, throw a 403
if (!match) {
const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD);
error.status = 403;
next(error);
return;
}
// If a match, update the password
req.body.password = req.body.newPassword;
}
try {
// Change Password check
if (req.body.password && req.body.newPassword) {
// Get token from headers
const token = getTokenFromHeaders(req.headers);
// Get email from token
const { jwtSecret } = req.settingsService.getSettings();
const { email } = jwt.verify(token, jwtSecret);
// Add user email to body for DB operation
req.body.email = email;
// Get user
const user = await req.db.getUserByEmail(email);
// Compare passwords
const match = await user.comparePassword(req.body.password);
// If not a match, throw a 403
if (!match) {
const error = new Error(errorMessages.AUTH_INCORRECT_PASSWORD);
error.status = 403;
next(error);
return;
}
// If a match, update the password
req.body.password = req.body.newPassword;
}
const updatedUser = await req.db.updateUser(req, res);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_UPDATE_USER,
data: updatedUser,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "userEditController"));
}
const updatedUser = await req.db.updateUser(req, res);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_UPDATE_USER,
data: updatedUser,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "userEditController"));
}
};
/**
@@ -244,17 +251,17 @@ const editUser = async (req, res, next) => {
* @returns {Object} The response object with a success status, a message indicating the existence of a superadmin, and a boolean indicating the existence of a superadmin.
* @throws {Error} If there is an error during the process.
*/
const checkSuperAdminExists = async (req, res, next) => {
try {
const superAdminExists = await req.db.checkSuperadmin(req, res);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_ADMIN_EXISTS,
data: superAdminExists,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "checkSuperadminController"));
}
const checkSuperadminExists = async (req, res, next) => {
try {
const superAdminExists = await req.db.checkSuperadmin(req, res);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_ADMIN_EXISTS,
data: superAdminExists,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "checkSuperadminController"));
}
};
/**
@@ -269,46 +276,46 @@ const checkSuperAdminExists = async (req, res, next) => {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
const requestRecovery = async (req, res, next) => {
try {
await recoveryValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
try {
await recoveryValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
try {
const { email } = req.body;
const user = await req.db.getUserByEmail(email);
if (user) {
const recoveryToken = await req.db.requestRecoveryToken(req, res);
const name = user.firstName;
const email = req.body.email;
const { clientHost } = req.settingsService.getSettings();
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
try {
const { email } = req.body;
const user = await req.db.getUserByEmail(email);
if (user) {
const recoveryToken = await req.db.requestRecoveryToken(req, res);
const name = user.firstName;
const email = req.body.email;
const { clientHost } = req.settingsService.getSettings();
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
const msgId = await req.emailService.buildAndSendEmail(
"passwordResetTemplate",
{
name,
email,
url,
},
email,
"Bluewave Uptime Password Reset"
);
const msgId = await req.emailService.buildAndSendEmail(
"passwordResetTemplate",
{
name,
email,
url,
},
email,
"Bluewave Uptime Password Reset"
);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN,
data: msgId,
});
} else {
throw new Error(errorMessages.FRIENDLY_ERROR);
}
} catch (error) {
next(handleError(error, SERVICE_NAME, "recoveryRequestController"));
}
return res.status(200).json({
success: true,
msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN,
data: msgId,
});
} else {
throw new Error(errorMessages.FRIENDLY_ERROR);
}
} catch (error) {
next(handleError(error, SERVICE_NAME, "recoveryRequestController"));
}
};
/**
@@ -323,23 +330,23 @@ const requestRecovery = async (req, res, next) => {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
const validateRecovery = async (req, res, next) => {
try {
await recoveryTokenValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
try {
await recoveryTokenValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
try {
await req.db.validateRecoveryToken(req, res);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "validateRecoveryTokenController"));
}
try {
await req.db.validateRecoveryToken(req, res);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "validateRecoveryTokenController"));
}
};
/**
@@ -355,26 +362,26 @@ const validateRecovery = async (req, res, next) => {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
const resetPassword = async (req, res, next) => {
try {
await newPasswordValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
try {
const user = await req.db.resetPassword(req, res);
try {
await newPasswordValidation.validateAsync(req.body);
} catch (error) {
const validationError = handleValidationError(error, SERVICE_NAME);
next(validationError);
return;
}
try {
const user = await req.db.resetPassword(req, res);
const appSettings = await req.settingsService.getSettings();
const token = issueToken(user._doc, tokenType.ACCESS_TOKEN, appSettings);
res.status(200).json({
success: true,
msg: successMessages.AUTH_RESET_PASSWORD,
data: { user, token },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "resetPasswordController"));
}
const appSettings = await req.settingsService.getSettings();
const token = issueToken(user._doc, tokenType.ACCESS_TOKEN, appSettings);
res.status(200).json({
success: true,
msg: successMessages.AUTH_RESET_PASSWORD,
data: { user, token },
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "resetPasswordController"));
}
};
/**
@@ -387,74 +394,72 @@ const resetPassword = async (req, res, next) => {
* @throws {Error} If user validation fails or user is not found in the database.
*/
const deleteUser = async (req, res, next) => {
try {
const token = getTokenFromHeaders(req.headers);
const decodedToken = jwt.decode(token);
const { email } = decodedToken;
try {
const token = getTokenFromHeaders(req.headers);
const decodedToken = jwt.decode(token);
const { email } = decodedToken;
// Check if the user exists
const user = await req.db.getUserByEmail(email);
if (!user) {
next(new Error(errorMessages.DB_USER_NOT_FOUND));
return;
}
// Check if the user exists
const user = await req.db.getUserByEmail(email);
if (!user) {
next(new Error(errorMessages.DB_USER_NOT_FOUND));
return;
}
// 1. Find all the monitors associated with the team ID if superadmin
// 1. Find all the monitors associated with the team ID if superadmin
const result = await req.db.getMonitorsByTeamId({
params: { teamId: user.teamId },
});
const result = await req.db.getMonitorsByTeamId({
params: { teamId: user.teamId },
});
if (user.role.includes("superadmin")) {
//2. Remove all jobs, delete checks and alerts
result?.monitors.length > 0 &&
(await Promise.all(
result.monitors.map(async (monitor) => {
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
})
));
if (user.role.includes("superadmin")) {
//2. Remove all jobs, delete checks and alerts
result?.monitors.length > 0 &&
(await Promise.all(
result.monitors.map(async (monitor) => {
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
})
));
// 3. Delete team
await req.db.deleteTeam(user.teamId);
// 4. Delete all other team members
await req.db.deleteAllOtherUsers();
// 5. Delete each monitor
await req.db.deleteMonitorsByUserId(user._id);
}
// 6. Delete the user by id
await req.db.deleteUser(user._id);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_DELETE_USER,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteUserController"));
}
// 3. Delete team
await req.db.deleteTeam(user.teamId);
// 4. Delete all other team members
await req.db.deleteAllOtherUsers();
// 5. Delete each monitor
await req.db.deleteMonitorsByUserId(user._id);
}
// 6. Delete the user by id
await req.db.deleteUser(user._id);
return res.status(200).json({
success: true,
msg: successMessages.AUTH_DELETE_USER,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteUserController"));
}
};
const getAllUsers = async (req, res) => {
try {
const allUsers = await req.db.getAllUsers(req, res);
res
.status(200)
.json({ success: true, msg: "Got all users", data: allUsers });
} catch (error) {
next(handleError(error, SERVICE_NAME, "getAllUsersController"));
}
const getAllUsers = async (req, res, next) => {
try {
const allUsers = await req.db.getAllUsers(req, res);
res.status(200).json({ success: true, msg: "Got all users", data: allUsers });
} catch (error) {
next(handleError(error, SERVICE_NAME, "getAllUsersController"));
}
};
module.exports = {
issueToken,
registerUser,
loginUser,
editUser,
checkSuperadminExists: checkSuperAdminExists,
requestRecovery,
validateRecovery,
resetPassword,
deleteUser,
getAllUsers,
export {
issueToken,
registerUser,
loginUser,
editUser,
checkSuperadminExists,
requestRecovery,
validateRecovery,
resetPassword,
deleteUser,
getAllUsers,
};

View File

@@ -1,4 +1,4 @@
const {
import {
createCheckParamValidation,
createCheckBodyValidation,
getChecksParamValidation,
@@ -8,12 +8,13 @@ const {
deleteChecksParamValidation,
deleteChecksByTeamIdParamValidation,
updateChecksTTLBodyValidation,
} = require("../validation/joi");
const { successMessages } = require("../utils/messages");
const jwt = require("jsonwebtoken");
const { getTokenFromHeaders } = require("../utils/utils");
} from "../validation/joi.js";
import { successMessages } from "../utils/messages.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders } from "../utils/utils.js";
import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "checkController";
const { handleValidationError, handleError } = require("./controllerUtils");
const createCheck = async (req, res, next) => {
try {
@@ -143,7 +144,7 @@ const updateChecksTTL = async (req, res, next) => {
}
};
module.exports = {
export {
createCheck,
getChecks,
getTeamChecks,

View File

@@ -1,19 +1,43 @@
const handleValidationError = (error, serviceName) => {
error.status = 422;
error.service = serviceName;
error.message =
error.details?.[0]?.message || error.message || "Validation Error";
return error;
error.status = 422;
error.service = serviceName;
error.message = error.details?.[0]?.message || error.message || "Validation Error";
return error;
};
const handleError = (error, serviceName, method, status = 500) => {
error.status === undefined ? (error.status = status) : null;
error.service === undefined ? (error.service = serviceName) : null;
error.method === undefined ? (error.method = method) : null;
return error;
error.status === undefined ? (error.status = status) : null;
error.service === undefined ? (error.service = serviceName) : null;
error.method === undefined ? (error.method = method) : null;
return error;
};
module.exports = {
handleValidationError,
handleError,
const fetchMonitorCertificate = async (tls, monitor) => {
return new Promise((resolve, reject) => {
const monitorUrl = new URL(monitor.url);
const hostname = monitorUrl.hostname;
try {
let socket = tls.connect(
{
port: 443,
host: hostname,
servername: hostname, // this is required in case the server enabled SNI
},
() => {
try {
let x509Certificate = socket.getPeerX509Certificate();
resolve(x509Certificate);
} catch (error) {
reject(error);
} finally {
socket.end();
}
}
);
} catch (error) {
reject(error);
}
});
};
export { handleValidationError, handleError, fetchMonitorCertificate };

View File

@@ -1,14 +1,17 @@
const {
import {
inviteRoleValidation,
inviteBodyValidation,
inviteVerificationBodyValidation,
} = require("../validation/joi");
const logger = require("../utils/logger");
require("dotenv").config();
const jwt = require("jsonwebtoken");
const { handleError, handleValidationError } = require("./controllerUtils");
} from "../validation/joi.js";
import logger from "../utils/logger.js";
import dotenv from "dotenv";
import jwt from "jsonwebtoken";
import { handleError, handleValidationError } from "./controllerUtils.js";
import { getTokenFromHeaders } from "../utils/utils.js";
dotenv.config();
const SERVICE_NAME = "inviteController";
const { getTokenFromHeaders } = require("../utils/utils");
/**
* Issues an invitation to a new user. Only admins can invite new users. An invitation token is created and sent via email.
@@ -82,7 +85,4 @@ const inviteVerifyController = async (req, res, next) => {
}
};
module.exports = {
issueInvitation,
inviteVerifyController,
};
export { issueInvitation, inviteVerifyController };

View File

@@ -1,4 +1,4 @@
const {
import {
createMaintenanceWindowBodyValidation,
editMaintenanceWindowByIdParamValidation,
editMaintenanceByIdWindowBodyValidation,
@@ -6,11 +6,12 @@ const {
getMaintenanceWindowsByMonitorIdParamValidation,
getMaintenanceWindowsByTeamIdQueryValidation,
deleteMaintenanceWindowByIdParamValidation,
} = require("../validation/joi");
const jwt = require("jsonwebtoken");
const { getTokenFromHeaders } = require("../utils/utils");
const { successMessages } = require("../utils/messages");
const { handleValidationError, handleError } = require("./controllerUtils");
} from "../validation/joi.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders } from "../utils/utils.js";
import { successMessages } from "../utils/messages.js";
import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "maintenanceWindowController";
const createMaintenanceWindows = async (req, res, next) => {
@@ -160,7 +161,7 @@ const editMaintenanceWindow = async (req, res, next) => {
}
};
module.exports = {
export {
createMaintenanceWindows,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,

View File

@@ -1,25 +1,29 @@
const {
getMonitorByIdParamValidation,
getMonitorByIdQueryValidation,
getMonitorsByTeamIdValidation,
createMonitorBodyValidation,
editMonitorBodyValidation,
getMonitorsAndSummaryByTeamIdParamValidation,
getMonitorsAndSummaryByTeamIdQueryValidation,
getMonitorsByTeamIdQueryValidation,
pauseMonitorParamValidation,
getMonitorStatsByIdParamValidation,
getMonitorStatsByIdQueryValidation,
getCertificateParamValidation,
} = require("../validation/joi");
import {
getMonitorByIdParamValidation,
getMonitorByIdQueryValidation,
getMonitorsByTeamIdValidation,
createMonitorBodyValidation,
editMonitorBodyValidation,
getMonitorsAndSummaryByTeamIdParamValidation,
getMonitorsAndSummaryByTeamIdQueryValidation,
getMonitorsByTeamIdQueryValidation,
pauseMonitorParamValidation,
getMonitorStatsByIdParamValidation,
getMonitorStatsByIdQueryValidation,
getCertificateParamValidation,
} from "../validation/joi.js";
import * as tls from "tls";
const sslChecker = require("ssl-checker");
const SERVICE_NAME = "monitorController";
const { errorMessages, successMessages } = require("../utils/messages");
const jwt = require("jsonwebtoken");
const { getTokenFromHeaders } = require("../utils/utils");
const logger = require("../utils/logger");
const { handleError, handleValidationError } = require("./controllerUtils");
import { errorMessages, successMessages } from "../utils/messages.js";
import jwt from "jsonwebtoken";
import { getTokenFromHeaders } from "../utils/utils.js";
import logger from "../utils/logger.js";
import {
handleError,
handleValidationError,
fetchMonitorCertificate,
} from "./controllerUtils.js";
/**
* Returns all monitors
@@ -31,16 +35,16 @@ const { handleError, handleValidationError } = require("./controllerUtils");
* @throws {Error}
*/
const getAllMonitors = async (req, res, next) => {
try {
const monitors = await req.db.getAllMonitors();
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_ALL,
data: monitors,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getAllMonitors"));
}
try {
const monitors = await req.db.getAllMonitors();
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_ALL,
data: monitors,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getAllMonitors"));
}
};
/**
@@ -53,56 +57,55 @@ const getAllMonitors = async (req, res, next) => {
* @throws {Error}
*/
const getMonitorStatsById = async (req, res, next) => {
try {
await getMonitorStatsByIdParamValidation.validateAsync(req.params);
await getMonitorStatsByIdQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await getMonitorStatsByIdParamValidation.validateAsync(req.params);
await getMonitorStatsByIdQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const monitorStats = await req.db.getMonitorStatsById(req);
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_STATS_BY_ID,
data: monitorStats,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorStatsById"));
}
try {
const monitorStats = await req.db.getMonitorStatsById(req);
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_STATS_BY_ID,
data: monitorStats,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorStatsById"));
}
};
const getMonitorCertificate = async (req, res, next) => {
try {
await getCertificateParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
}
const getMonitorCertificate = async (req, res, next, fetchMonitorCertificate) => {
try {
await getCertificateParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
}
try {
const { monitorId } = req.params;
const monitor = await req.db.getMonitorById(monitorId);
const monitorUrl = new URL(monitor.url);
const certificate = await sslChecker(monitorUrl.hostname);
if (certificate && certificate.validTo) {
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_CERTIFICATE,
data: {
certificateDate: new Date(certificate.validTo).toLocaleDateString(),
},
});
} else {
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_CERTIFICATE,
data: { certificateDate: "N/A" },
});
}
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorCertificate"));
}
try {
const { monitorId } = req.params;
const monitor = await req.db.getMonitorById(monitorId);
const certificate = await fetchMonitorCertificate(tls, monitor);
if (certificate && certificate.validTo) {
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_CERTIFICATE,
data: {
certificateDate: new Date(certificate.validTo),
},
});
} else {
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_CERTIFICATE,
data: { certificateDate: "N/A" },
});
}
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorCertificate"));
}
};
/**
@@ -117,30 +120,30 @@ const getMonitorCertificate = async (req, res, next) => {
* @throws {Error} If there is an error during the process, especially if the monitor is not found (404) or if there is a validation error (422).
*/
const getMonitorById = async (req, res, next) => {
try {
await getMonitorByIdParamValidation.validateAsync(req.params);
await getMonitorByIdQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await getMonitorByIdParamValidation.validateAsync(req.params);
await getMonitorByIdQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const monitor = await req.db.getMonitorById(req.params.monitorId);
if (!monitor) {
const error = new Error(errorMessages.MONITOR_GET_BY_ID);
error.status = 404;
throw error;
}
try {
const monitor = await req.db.getMonitorById(req.params.monitorId);
if (!monitor) {
const error = new Error(errorMessages.MONITOR_GET_BY_ID);
error.status = 404;
throw error;
}
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_BY_ID,
data: monitor,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorById"));
}
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_BY_ID,
data: monitor,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorById"));
}
};
/**
@@ -157,31 +160,26 @@ const getMonitorById = async (req, res, next) => {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
const getMonitorsAndSummaryByTeamId = async (req, res, next) => {
try {
await getMonitorsAndSummaryByTeamIdParamValidation.validateAsync(
req.params
);
await getMonitorsAndSummaryByTeamIdQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await getMonitorsAndSummaryByTeamIdParamValidation.validateAsync(req.params);
await getMonitorsAndSummaryByTeamIdQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const { teamId } = req.params;
const { type } = req.query;
const monitorsSummary = await req.db.getMonitorsAndSummaryByTeamId(
teamId,
type
);
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_BY_USER_ID(teamId),
data: monitorsSummary,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorsAndSummaryByTeamId"));
}
try {
const { teamId } = req.params;
const { type } = req.query;
const monitorsSummary = await req.db.getMonitorsAndSummaryByTeamId(teamId, type);
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_BY_USER_ID(teamId),
data: monitorsSummary,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorsAndSummaryByTeamId"));
}
};
/**
@@ -197,26 +195,26 @@ const getMonitorsAndSummaryByTeamId = async (req, res, next) => {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
const getMonitorsByTeamId = async (req, res, next) => {
try {
await getMonitorsByTeamIdValidation.validateAsync(req.params);
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await getMonitorsByTeamIdValidation.validateAsync(req.params);
await getMonitorsByTeamIdQueryValidation.validateAsync(req.query);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const teamId = req.params.teamId;
const monitors = await req.db.getMonitorsByTeamId(req, res);
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_BY_USER_ID(teamId),
data: monitors,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId"));
next(error);
}
try {
const teamId = req.params.teamId;
const monitors = await req.db.getMonitorsByTeamId(req, res);
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_GET_BY_USER_ID(teamId),
data: monitors,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "getMonitorsByTeamId"));
next(error);
}
};
/**
@@ -231,36 +229,36 @@ const getMonitorsByTeamId = async (req, res, next) => {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
const createMonitor = async (req, res, next) => {
try {
await createMonitorBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await createMonitorBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const notifications = req.body.notifications;
const monitor = await req.db.createMonitor(req, res);
try {
const notifications = req.body.notifications;
const monitor = await req.db.createMonitor(req, res);
if (notifications && notifications.length !== 0) {
monitor.notifications = await Promise.all(
notifications.map(async (notification) => {
notification.monitorId = monitor._id;
await req.db.createNotification(notification);
})
);
await monitor.save();
}
// Add monitor to job queue
req.jobQueue.addJob(monitor._id, monitor);
return res.status(201).json({
success: true,
msg: successMessages.MONITOR_CREATE,
data: monitor,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "createMonitor"));
}
if (notifications && notifications.length !== 0) {
monitor.notifications = await Promise.all(
notifications.map(async (notification) => {
notification.monitorId = monitor._id;
await req.db.createNotification(notification);
})
);
await monitor.save();
}
// Add monitor to job queue
req.jobQueue.addJob(monitor._id, monitor);
return res.status(201).json({
success: true,
msg: successMessages.MONITOR_CREATE,
data: monitor,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "createMonitor"));
}
};
/**
@@ -275,37 +273,35 @@ const createMonitor = async (req, res, next) => {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422) or an error in deleting associated records.
*/
const deleteMonitor = async (req, res, next) => {
try {
await getMonitorByIdParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await getMonitorByIdParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const monitor = await req.db.deleteMonitor(req, res, next);
// Delete associated checks,alerts,and notifications
try {
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
} catch (error) {
logger.error(
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
{
method: "deleteMonitor",
service: SERVICE_NAME,
error: error.message,
}
);
}
return res
.status(200)
.json({ success: true, msg: successMessages.MONITOR_DELETE });
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteMonitor"));
}
try {
const monitor = await req.db.deleteMonitor(req, res, next);
// Delete associated checks,alerts,and notifications
try {
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
} catch (error) {
logger.error(
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
{
method: "deleteMonitor",
service: SERVICE_NAME,
error: error.message,
}
);
}
return res.status(200).json({ success: true, msg: successMessages.MONITOR_DELETE });
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteMonitor"));
}
};
/**
@@ -320,34 +316,34 @@ const deleteMonitor = async (req, res, next) => {
* @throws {Error} If there is an error during the deletion process.
*/
const deleteAllMonitors = async (req, res, next) => {
try {
const token = getTokenFromHeaders(req.headers);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const { monitors, deletedCount } = await req.db.deleteAllMonitors(teamId);
await Promise.all(
monitors.map(async (monitor) => {
try {
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
} catch (error) {
logger.error(
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
{
method: "deleteAllMonitors",
}
);
}
})
);
return res
.status(200)
.json({ success: true, msg: `Deleted ${deletedCount} monitors` });
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteAllMonitors"));
}
try {
const token = getTokenFromHeaders(req.headers);
const { jwtSecret } = req.settingsService.getSettings();
const { teamId } = jwt.verify(token, jwtSecret);
const { monitors, deletedCount } = await req.db.deleteAllMonitors(teamId);
await Promise.all(
monitors.map(async (monitor) => {
try {
await req.jobQueue.deleteJob(monitor);
await req.db.deleteChecks(monitor._id);
await req.db.deletePageSpeedChecksByMonitorId(monitor._id);
await req.db.deleteNotificationsByMonitorId(monitor._id);
} catch (error) {
logger.error(
`Error deleting associated records for monitor ${monitor._id} with name ${monitor.name}`,
{
method: "deleteAllMonitors",
}
);
}
})
);
return res
.status(200)
.json({ success: true, msg: `Deleted ${deletedCount} monitors` });
} catch (error) {
next(handleError(error, SERVICE_NAME, "deleteAllMonitors"));
}
};
/**
@@ -364,46 +360,46 @@ const deleteAllMonitors = async (req, res, next) => {
* @throws {Error} If there is an error during the process, especially if there is a validation error (422).
*/
const editMonitor = async (req, res, next) => {
try {
await getMonitorByIdParamValidation.validateAsync(req.params);
await editMonitorBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
await getMonitorByIdParamValidation.validateAsync(req.params);
await editMonitorBodyValidation.validateAsync(req.body);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
return;
}
try {
const { monitorId } = req.params;
const monitorBeforeEdit = await req.db.getMonitorById(monitorId);
try {
const { monitorId } = req.params;
const monitorBeforeEdit = await req.db.getMonitorById(monitorId);
// Get notifications from the request body
const notifications = req.body.notifications;
// Get notifications from the request body
const notifications = req.body.notifications;
const editedMonitor = await req.db.editMonitor(monitorId, req.body);
const editedMonitor = await req.db.editMonitor(monitorId, req.body);
await req.db.deleteNotificationsByMonitorId(editedMonitor._id);
await req.db.deleteNotificationsByMonitorId(editedMonitor._id);
if (notifications && notifications.length !== 0) {
await Promise.all(
notifications.map(async (notification) => {
notification.monitorId = editedMonitor._id;
await req.db.createNotification(notification);
})
);
}
if (notifications && notifications.length !== 0) {
await Promise.all(
notifications.map(async (notification) => {
notification.monitorId = editedMonitor._id;
await req.db.createNotification(notification);
})
);
}
// Delete the old job(editedMonitor has the same ID as the old monitor)
await req.jobQueue.deleteJob(monitorBeforeEdit);
// Add the new job back to the queue
await req.jobQueue.addJob(editedMonitor._id, editedMonitor);
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_EDIT,
data: editedMonitor,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "editMonitor"));
}
// Delete the old job(editedMonitor has the same ID as the old monitor)
await req.jobQueue.deleteJob(monitorBeforeEdit);
// Add the new job back to the queue
await req.jobQueue.addJob(editedMonitor._id, editedMonitor);
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_EDIT,
data: editedMonitor,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "editMonitor"));
}
};
/**
@@ -418,32 +414,32 @@ const editMonitor = async (req, res, next) => {
* @throws {Error} If there is an error during the process.
*/
const pauseMonitor = async (req, res, next) => {
try {
await pauseMonitorParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
}
try {
await pauseMonitorParamValidation.validateAsync(req.params);
} catch (error) {
next(handleValidationError(error, SERVICE_NAME));
}
try {
const monitor = await req.db.getMonitorById(req.params.monitorId);
if (monitor.isActive) {
await req.jobQueue.deleteJob(monitor);
} else {
await req.jobQueue.addJob(monitor._id, monitor);
}
monitor.isActive = !monitor.isActive;
monitor.status = undefined;
monitor.save();
return res.status(200).json({
success: true,
msg: monitor.isActive
? successMessages.MONITOR_RESUME
: successMessages.MONITOR_PAUSE,
data: monitor,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "pauseMonitor"));
}
try {
const monitor = await req.db.getMonitorById(req.params.monitorId);
if (monitor.isActive) {
await req.jobQueue.deleteJob(monitor);
} else {
await req.jobQueue.addJob(monitor._id, monitor);
}
monitor.isActive = !monitor.isActive;
monitor.status = undefined;
monitor.save();
return res.status(200).json({
success: true,
msg: monitor.isActive
? successMessages.MONITOR_RESUME
: successMessages.MONITOR_PAUSE,
data: monitor,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "pauseMonitor"));
}
};
/**
@@ -458,36 +454,36 @@ const pauseMonitor = async (req, res, next) => {
* @throws {Error} If there is an error during the process.
*/
const addDemoMonitors = async (req, res, next) => {
try {
const token = getTokenFromHeaders(req.headers);
const { jwtSecret } = req.settingsService.getSettings();
const { _id, teamId } = jwt.verify(token, jwtSecret);
const demoMonitors = await req.db.addDemoMonitors(_id, teamId);
await Promise.all(
demoMonitors.map((monitor) => req.jobQueue.addJob(monitor._id, monitor))
);
try {
const token = getTokenFromHeaders(req.headers);
const { jwtSecret } = req.settingsService.getSettings();
const { _id, teamId } = jwt.verify(token, jwtSecret);
const demoMonitors = await req.db.addDemoMonitors(_id, teamId);
await Promise.all(
demoMonitors.map((monitor) => req.jobQueue.addJob(monitor._id, monitor))
);
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_DEMO_ADDED,
data: demoMonitors.length,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "addDemoMonitors"));
}
return res.status(200).json({
success: true,
msg: successMessages.MONITOR_DEMO_ADDED,
data: demoMonitors.length,
});
} catch (error) {
next(handleError(error, SERVICE_NAME, "addDemoMonitors"));
}
};
module.exports = {
getAllMonitors,
getMonitorStatsById,
getMonitorCertificate,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
editMonitor,
pauseMonitor,
addDemoMonitors,
export {
getAllMonitors,
getMonitorStatsById,
getMonitorCertificate,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
editMonitor,
pauseMonitor,
addDemoMonitors,
};

View File

@@ -1,5 +1,6 @@
const { handleError } = require("./controllerUtils");
const { errorMessages, successMessages } = require("../utils/messages");
import { handleError } from "./controllerUtils.js";
import { errorMessages, successMessages } from "../utils/messages.js";
const SERVICE_NAME = "JobQueueController";
const getMetrics = async (req, res, next) => {
@@ -55,9 +56,4 @@ const obliterateQueue = async (req, res, next) => {
}
};
module.exports = {
getMetrics,
getJobs,
addJob,
obliterateQueue,
};
export { getMetrics, getJobs, addJob, obliterateQueue };

View File

@@ -1,7 +1,7 @@
const { successMessages } = require("../utils/messages");
import { successMessages } from "../utils/messages.js";
import { updateAppSettingsBodyValidation } from "../validation/joi.js";
import { handleValidationError, handleError } from "./controllerUtils.js";
const SERVICE_NAME = "SettingsController";
const { updateAppSettingsBodyValidation } = require("../validation/joi");
const { handleValidationError, handleError } = require("./controllerUtils");
const getAppSettings = async (req, res, next) => {
try {
@@ -39,7 +39,4 @@ const updateAppSettings = async (req, res, next) => {
}
};
module.exports = {
getAppSettings,
updateAppSettings,
};
export { getAppSettings, updateAppSettings };

View File

@@ -1,4 +1,4 @@
const mongoose = require("mongoose");
import mongoose from "mongoose";
const AppSettingsSchema = mongoose.Schema(
{
@@ -88,4 +88,4 @@ const AppSettingsSchema = mongoose.Schema(
}
);
module.exports = mongoose.model("AppSettings", AppSettingsSchema);
export default mongoose.model("AppSettings", AppSettingsSchema);

View File

@@ -1,6 +1,6 @@
const mongoose = require("mongoose");
const EmailService = require("../../service/emailService");
const Notification = require("./Notification");
import mongoose from "mongoose";
import EmailService from "../../service/emailService.js";
import Notification from "./Notification.js";
/**
* Check Schema for MongoDB collection.
@@ -73,5 +73,4 @@ const CheckSchema = mongoose.Schema(
);
CheckSchema.index({ createdAt: 1 });
module.exports = mongoose.model("Check", CheckSchema);
export default mongoose.model("Check", CheckSchema);

View File

@@ -1,5 +1,4 @@
const mongoose = require("mongoose");
import mongoose from "mongoose";
const InviteTokenSchema = mongoose.Schema(
{
email: {
@@ -32,4 +31,4 @@ const InviteTokenSchema = mongoose.Schema(
}
);
module.exports = mongoose.model("InviteToken", InviteTokenSchema);
export default mongoose.model("InviteToken", InviteTokenSchema);

View File

@@ -1,5 +1,4 @@
const mongoose = require("mongoose");
import mongoose from "mongoose";
/**
* MaintenanceWindow Schema
* @module MaintenanceWindow
@@ -66,4 +65,4 @@ const MaintenanceWindow = mongoose.Schema(
}
);
module.exports = mongoose.model("MaintenanceWindow", MaintenanceWindow);
export default mongoose.model("MaintenanceWindow", MaintenanceWindow);

View File

@@ -1,5 +1,5 @@
const mongoose = require("mongoose");
const Notification = require("./Notification");
import mongoose from "mongoose";
import Notification from "./Notification.js";
const MonitorSchema = mongoose.Schema(
{
@@ -60,4 +60,4 @@ const MonitorSchema = mongoose.Schema(
}
);
module.exports = mongoose.model("Monitor", MonitorSchema);
export default mongoose.model("Monitor", MonitorSchema);

View File

@@ -1,5 +1,4 @@
const mongoose = require("mongoose");
import mongoose from "mongoose";
const NotificationSchema = mongoose.Schema(
{
monitorId: {
@@ -22,5 +21,4 @@ const NotificationSchema = mongoose.Schema(
timestamps: true,
}
);
module.exports = mongoose.model("Notification", NotificationSchema);
export default mongoose.model("Notification", NotificationSchema);

View File

@@ -1,5 +1,4 @@
const mongoose = require("mongoose");
import mongoose from "mongoose";
const AuditSchema = mongoose.Schema({
id: { type: String, required: true },
title: { type: String, required: true },
@@ -109,4 +108,4 @@ PageSpeedCheck.pre("save", async function (next) {
}
});
module.exports = mongoose.model("PageSpeedCheck", PageSpeedCheck);
export default mongoose.model("PageSpeedCheck", PageSpeedCheck);

View File

@@ -1,4 +1,4 @@
const mongoose = require("mongoose");
import mongoose from "mongoose";
const RecoveryTokenSchema = mongoose.Schema(
{
@@ -22,4 +22,4 @@ const RecoveryTokenSchema = mongoose.Schema(
}
);
module.exports = mongoose.model("RecoveryToken", RecoveryTokenSchema);
export default mongoose.model("RecoveryToken", RecoveryTokenSchema);

View File

@@ -1,5 +1,4 @@
const mongoose = require("mongoose");
import mongoose from "mongoose";
const TeamSchema = mongoose.Schema(
{
email: {
@@ -12,5 +11,4 @@ const TeamSchema = mongoose.Schema(
timestamps: true,
}
);
module.exports = mongoose.model("Team", TeamSchema);
export default mongoose.model("Team", TeamSchema);

View File

@@ -1,87 +1,87 @@
const mongoose = require("mongoose");
const bcrypt = require("bcrypt");
import mongoose from "mongoose";
import bcrypt from "bcrypt";
const UserSchema = mongoose.Schema(
{
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
avatarImage: {
type: String,
},
profileImage: {
data: Buffer,
contentType: String,
},
isActive: {
type: Boolean,
default: true,
},
isVerified: {
type: Boolean,
default: false,
},
role: {
type: [String],
default: "user",
enum: ["user", "admin", "superadmin", "demo"],
},
teamId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Team",
immutable: true,
},
checkTTL: {
type: Number,
},
},
{
timestamps: true,
}
{
firstName: {
type: String,
required: true,
},
lastName: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
avatarImage: {
type: String,
},
profileImage: {
data: Buffer,
contentType: String,
},
isActive: {
type: Boolean,
default: true,
},
isVerified: {
type: Boolean,
default: false,
},
role: {
type: [String],
default: "user",
enum: ["user", "admin", "superadmin", "demo"],
},
teamId: {
type: mongoose.Schema.Types.ObjectId,
ref: "Team",
immutable: true,
},
checkTTL: {
type: Number,
},
},
{
timestamps: true,
}
);
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 = await bcrypt.hash(this.password, salt); // hash is also async, need to eitehr await or use hashSync
next();
if (!this.isModified("password")) {
next();
}
const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
this.password = await bcrypt.hash(this.password, salt); // hash is also async, need to eitehr await or use hashSync
next();
});
UserSchema.pre("findOneAndUpdate", async function (next) {
const update = this.getUpdate();
if ("password" in update) {
const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
update.password = await bcrypt.hash(update.password, salt); // hash is also async, need to eitehr await or use hashSync
}
const update = this.getUpdate();
if ("password" in update) {
const salt = await bcrypt.genSalt(10); //genSalt is asynchronous, need to wait
update.password = await bcrypt.hash(update.password, salt); // hash is also async, need to eitehr await or use hashSync
}
next();
next();
});
UserSchema.methods.comparePassword = async function (submittedPassword) {
res = await bcrypt.compare(submittedPassword, this.password);
return res;
const res = await bcrypt.compare(submittedPassword, this.password);
return res;
};
const User = mongoose.model("User", UserSchema);
User.init().then(() => {
console.log("User model initialized");
console.log("User model initialized");
});
module.exports = mongoose.model("User", UserSchema);
export default mongoose.model("User", UserSchema);

View File

@@ -1,6 +1,6 @@
const mongoose = require("mongoose");
const UserModel = require("../models/User");
const AppSettings = require("../models/AppSettings");
import mongoose from "mongoose";
import UserModel from "../models/User.js";
import AppSettings from "../models/AppSettings.js";
//****************************************
// DB Connection
@@ -41,7 +41,7 @@ const checkSuperadmin = async (req, res) => {
// User Operations
//****************************************
const {
import {
insertUser,
getUserByEmail,
updateUser,
@@ -50,32 +50,32 @@ const {
deleteAllOtherUsers,
getAllUsers,
logoutUser,
} = require("./modules/userModule");
} from "./modules/userModule.js";
//****************************************
// Invite Token Operations
//****************************************
const {
import {
requestInviteToken,
getInviteToken,
getInviteTokenAndDelete,
} = require("./modules/inviteModule");
} from "./modules/inviteModule.js";
//****************************************
// Recovery Operations
//****************************************
const {
import {
requestRecoveryToken,
validateRecoveryToken,
resetPassword,
} = require("./modules/recoveryModule");
} from "./modules/recoveryModule.js";
//****************************************
// Monitors
//****************************************
const {
import {
getAllMonitors,
getMonitorStatsById,
getMonitorById,
@@ -87,23 +87,23 @@ const {
deleteMonitorsByUserId,
editMonitor,
addDemoMonitors,
} = require("./modules/monitorModule");
} from "./modules/monitorModule.js";
//****************************************
// Page Speed Checks
//****************************************
const {
import {
createPageSpeedCheck,
getPageSpeedChecks,
deletePageSpeedChecksByMonitorId,
} = require("./modules/pageSpeedCheckModule");
} from "./modules/pageSpeedCheckModule.js";
//****************************************
// Checks
//****************************************
const {
import {
createCheck,
getChecksCount,
getChecks,
@@ -111,12 +111,12 @@ const {
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
} = require("./modules/checkModule");
} from "./modules/checkModule.js";
//****************************************
// Maintenance Window
//****************************************
const {
import {
createMaintenanceWindow,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
@@ -125,26 +125,23 @@ const {
deleteMaintenanceWindowByMonitorId,
deleteMaintenanceWindowByUserId,
editMaintenanceWindowById,
} = require("./modules/maintenanceWindowModule");
} from "./modules/maintenanceWindowModule.js";
//****************************************
// Notifications
//****************************************
const {
import {
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,
} = require("./modules/notificationModule");
} from "./modules/notificationModule.js";
//****************************************
// AppSettings
//****************************************
const {
getAppSettings,
updateAppSettings,
} = require("./modules/settingsModule");
import { getAppSettings, updateAppSettings } from "./modules/settingsModule.js";
module.exports = {
export default {
connect,
insertUser,
getUserByEmail,

View File

@@ -1,7 +1,7 @@
const Check = require("../../models/Check");
const Monitor = require("../../models/Monitor");
const User = require("../../models/User");
const logger = require("../../../utils/logger");
import Check from "../../models/Check.js";
import Monitor from "../../models/Monitor.js";
import User from "../../models/User.js";
import logger from "../../../utils/logger.js";
const SERVICE_NAME = "checkModule";
const dateRangeLookup = {
day: new Date(new Date().setDate(new Date().getDate() - 1)),
@@ -274,7 +274,7 @@ const updateChecksTTL = async (teamId, ttl) => {
}
};
module.exports = {
export {
createCheck,
getChecksCount,
getChecks,

View File

@@ -1,8 +1,8 @@
const InviteToken = require("../../models/InviteToken");
const crypto = require("crypto");
const { errorMessages } = require("../../../utils/messages");
const SERVICE_NAME = "inviteModule";
import InviteToken from "../../models/InviteToken.js";
import crypto from "crypto";
import { errorMessages } from "../../../utils/messages.js";
const SERVICE_NAME = "inviteModule";
/**
* Request an invite token for a user.
*
@@ -83,8 +83,4 @@ const getInviteTokenAndDelete = async (token) => {
}
};
module.exports = {
requestInviteToken,
getInviteToken,
getInviteTokenAndDelete,
};
export { requestInviteToken, getInviteToken, getInviteTokenAndDelete };

View File

@@ -1,4 +1,4 @@
const MaintenanceWindow = require("../../models/MaintenanceWindow");
import MaintenanceWindow from "../../models/MaintenanceWindow.js";
const SERVICE_NAME = "maintenanceWindowModule";
/**
@@ -220,7 +220,7 @@ const editMaintenanceWindowById = async (
}
};
module.exports = {
export {
createMaintenanceWindow,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,

View File

@@ -1,10 +1,22 @@
const Monitor = require("../../models/Monitor");
const Check = require("../../models/Check");
const PageSpeedCheck = require("../../models/PageSpeedCheck");
const { errorMessages } = require("../../../utils/messages");
const Notification = require("../../models/Notification");
const { NormalizeData } = require("../../../utils/dataUtils");
const demoMonitors = require("../../../utils/demoMonitors.json");
import Monitor from "../../models/Monitor.js";
import Check from "../../models/Check.js";
import PageSpeedCheck from "../../models/PageSpeedCheck.js";
import { errorMessages } from "../../../utils/messages.js";
import Notification from "../../models/Notification.js";
import { NormalizeData } from "../../../utils/dataUtils.js";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const demoMonitorsPath = path.resolve(
__dirname,
"../../../utils/demoMonitors.json"
);
const demoMonitors = JSON.parse(fs.readFileSync(demoMonitorsPath, "utf8"));
const SERVICE_NAME = "monitorModule";
/**
@@ -572,7 +584,7 @@ const addDemoMonitors = async (userId, teamId) => {
}
};
module.exports = {
export {
getAllMonitors,
getMonitorStatsById,
getMonitorById,

View File

@@ -1,4 +1,4 @@
const Notification = require("../../models/Notification");
import Notification from "../../models/Notification.js";
const SERVICE_NAME = "notificationModule";
/**
* Creates a new notification.
@@ -49,7 +49,7 @@ const deleteNotificationsByMonitorId = async (monitorId) => {
}
};
module.exports = {
export {
createNotification,
getNotificationsByMonitorId,
deleteNotificationsByMonitorId,

View File

@@ -1,4 +1,4 @@
const PageSpeedCheck = require("../../models/PageSpeedCheck");
import PageSpeedCheck from "../../models/PageSpeedCheck.js";
const SERVICE_NAME = "pageSpeedCheckModule";
/**
* Create a PageSpeed check for a monitor
@@ -63,7 +63,7 @@ const deletePageSpeedChecksByMonitorId = async (monitorId) => {
}
};
module.exports = {
export {
createPageSpeedCheck,
getPageSpeedChecks,
deletePageSpeedChecksByMonitorId,

View File

@@ -1,7 +1,8 @@
const UserModel = require("../../models/User");
const RecoveryToken = require("../../models/RecoveryToken");
const crypto = require("crypto");
const { errorMessages } = require("../../../utils/messages");
import UserModel from "../../models/User.js";
import RecoveryToken from "../../models/RecoveryToken.js";
import crypto from "crypto";
import { errorMessages } from "../../../utils/messages.js";
const SERVICE_NAME = "recoveryModule";
/**
@@ -81,8 +82,4 @@ const resetPassword = async (req, res) => {
}
};
module.exports = {
requestRecoveryToken,
validateRecoveryToken,
resetPassword,
};
export { requestRecoveryToken, validateRecoveryToken, resetPassword };

View File

@@ -1,4 +1,4 @@
const AppSettings = require("../../models/AppSettings");
import AppSettings from "../../models/AppSettings.js";
const SERVICE_NAME = "SettingsModule";
const getAppSettings = async () => {
@@ -27,7 +27,4 @@ const updateAppSettings = async (newSettings) => {
}
};
module.exports = {
getAppSettings,
updateAppSettings,
};
export { getAppSettings, updateAppSettings };

View File

@@ -1,11 +1,11 @@
const UserModel = require("../../models/User");
const TeamModel = require("../../models/Team");
const { errorMessages } = require("../../../utils/messages");
const { GenerateAvatarImage } = require("../../../utils/imageProcessing");
import UserModel from "../../models/User.js";
import TeamModel from "../../models/Team.js";
import { errorMessages } from "../../../utils/messages.js";
import { GenerateAvatarImage } from "../../../utils/imageProcessing.js";
const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key
const { ParseBoolean } = require("../../../utils/utils");
SERVICE_NAME = "userModule";
import { ParseBoolean } from "../../../utils/utils.js";
const SERVICE_NAME = "userModule";
/**
* Insert a User
@@ -208,7 +208,7 @@ const logoutUser = async (userId) => {
}
};
module.exports = {
export {
insertUser,
getUserByEmail,
updateUser,

View File

@@ -1,163 +1,165 @@
const path = require("path");
const fs = require("fs");
const swaggerUi = require("swagger-ui-express");
import path from "path";
import fs from "fs";
import swaggerUi from "swagger-ui-express";
const express = require("express");
const helmet = require("helmet");
const cors = require("cors");
const logger = require("./utils/logger");
const { verifyJWT } = require("./middleware/verifyJWT");
const { handleErrors } = require("./middleware/handleErrors");
const { errorMessages } = require("./utils/messages");
const authRouter = require("./routes/authRoute");
const inviteRouter = require("./routes/inviteRoute");
const monitorRouter = require("./routes/monitorRoute");
const checkRouter = require("./routes/checkRoute");
const maintenanceWindowRouter = require("./routes/maintenanceWindowRoute");
const settingsRouter = require("./routes/settingsRoute");
import express from "express";
import helmet from "helmet";
import cors from "cors";
import logger from "./utils/logger.js";
import { verifyJWT } from "./middleware/verifyJWT.js";
import { handleErrors } from "./middleware/handleErrors.js";
import { errorMessages } from "./utils/messages.js";
import authRouter from "./routes/authRoute.js";
import inviteRouter from "./routes/inviteRoute.js";
import monitorRouter from "./routes/monitorRoute.js";
import checkRouter from "./routes/checkRoute.js";
import maintenanceWindowRouter from "./routes/maintenanceWindowRoute.js";
import settingsRouter from "./routes/settingsRoute.js";
import { fileURLToPath } from "url";
const { connectDbAndRunServer } = require("./configs/db");
const queueRouter = require("./routes/queueRoute");
const JobQueue = require("./service/jobQueue");
const NetworkService = require("./service/networkService");
const EmailService = require("./service/emailService");
const SettingsService = require("./service/settingsService");
import { connectDbAndRunServer } from "./configs/db.js";
import queueRouter from "./routes/queueRoute.js";
import JobQueue from "./service/jobQueue.js";
import NetworkService from "./service/networkService.js";
import EmailService from "./service/emailService.js";
import SettingsService from "./service/settingsService.js";
import db from "./db/mongo/MongoDB.js";
import { fetchMonitorCertificate } from "./controllers/controllerUtils.js";
const SERVICE_NAME = "Server";
let cleaningUp = false;
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const openApiSpec = JSON.parse(
fs.readFileSync(path.join(__dirname, "openapi.json"), "utf8")
fs.readFileSync(path.join(__dirname, "openapi.json"), "utf8")
);
// Need to wrap server setup in a function to handle async nature of JobQueue
const startApp = async () => {
// **************************
// 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");
//
// **************************
const DB_TYPE = {
MongoDB: () => require("./db/mongo/MongoDB"),
FakedDB: () => require("./db/FakeDb"),
};
// **************************
// 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");
//
// **************************
// const DB_TYPE = {
// MongoDB: async () => (await import("./db/mongo/MongoDB.js")).default,
// FakedDB: async () => (await import("./db/FakeDb.js")).default,
// };
// const db = DB_TYPE[process.env.DB_TYPE]
// ? DB_TYPE[process.env.DB_TYPE]()
// : require("./db/FakeDb");
// const db = DB_TYPE[process.env.DB_TYPE]
// ? DB_TYPE[process.env.DB_TYPE]()
// : require("./db/FakeDb");
const db = DB_TYPE.MongoDB();
// const db = DB_TYPE.MongoDB();
const app = express();
const app = express();
// middlewares
app.use(
cors()
//We will add configuration later
);
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
// Same applies for JobQueue and emailService
// **************************
app.use((req, res, next) => {
req.db = db;
req.jobQueue = jobQueue;
req.emailService = emailService;
req.settingsService = settingsService;
next();
});
// middlewares
app.use(
cors()
//We will add configuration later
);
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
// Same applies for JobQueue and emailService
// **************************
app.use((req, res, next) => {
req.db = db;
req.jobQueue = jobQueue;
req.emailService = emailService;
req.settingsService = settingsService;
next();
});
// Swagger UI
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
// Swagger UI
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(openApiSpec));
//routes
app.use("/api/v1/auth", authRouter);
app.use("/api/v1/settings", verifyJWT, settingsRouter);
app.use("/api/v1/invite", inviteRouter);
app.use("/api/v1/monitors", verifyJWT, monitorRouter);
app.use("/api/v1/checks", verifyJWT, checkRouter);
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRouter);
app.use("/api/v1/queue", verifyJWT, queueRouter);
//routes
app.use("/api/v1/auth", authRouter);
app.use("/api/v1/settings", verifyJWT, settingsRouter);
app.use("/api/v1/invite", inviteRouter);
app.use("/api/v1/monitors", verifyJWT, monitorRouter);
app.use("/api/v1/checks", verifyJWT, checkRouter);
app.use("/api/v1/maintenance-window", verifyJWT, maintenanceWindowRouter);
app.use("/api/v1/queue", verifyJWT, queueRouter);
//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);
return res.status(500).json({ message: error.message });
}
});
//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);
return res.status(500).json({ message: error.message });
}
});
app.use("/api/v1/mail", async (req, res) => {
try {
const id = await req.emailService.buildAndSendEmail(
"welcomeEmailTemplate",
{
name: "Alex",
},
"ajhollid@gmail.com",
"Welcome"
);
res.status(200).json({ success: true, msg: "Email sent", data: id });
} catch (error) {
logger.error(error.message);
return res.status(500).json({ message: error.message });
}
});
app.use("/api/v1/mail", async (req, res) => {
try {
const id = await req.emailService.buildAndSendEmail(
"welcomeEmailTemplate",
{
name: "Alex",
},
"ajhollid@gmail.com",
"Welcome"
);
res.status(200).json({ success: true, msg: "Email sent", data: id });
} catch (error) {
logger.error(error.message);
return res.status(500).json({ message: error.message });
}
});
/**
* Error handler middleware
* Should be called last
*/
app.use(handleErrors);
/**
* Error handler middleware
* Should be called last
*/
app.use(handleErrors);
// Create services
await connectDbAndRunServer(app, db);
const settingsService = new SettingsService();
// Create services
await connectDbAndRunServer(app, db);
const settingsService = new SettingsService();
await settingsService.loadSettings();
const emailService = new EmailService(settingsService);
const networkService = new NetworkService(db, emailService);
const jobQueue = await JobQueue.createJobQueue(
db,
networkService,
settingsService
);
await settingsService.loadSettings();
const emailService = new EmailService(settingsService);
const networkService = new NetworkService(db, emailService);
const jobQueue = await JobQueue.createJobQueue(db, networkService, settingsService);
const cleanup = async () => {
if (cleaningUp) {
console.log("Already cleaning up");
return;
}
cleaningUp = true;
try {
console.log("Shutting down gracefully");
await jobQueue.obliterate();
console.log("Finished cleanup");
} catch (error) {
logger.error(errorMessages.JOB_QUEUE_DELETE_JOB, {
service: SERVICE_NAME,
errorMsg: error.message,
});
}
process.exit(0);
};
process.on("SIGUSR2", cleanup);
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
const cleanup = async () => {
if (cleaningUp) {
console.log("Already cleaning up");
return;
}
cleaningUp = true;
try {
console.log("Shutting down gracefully");
await jobQueue.obliterate();
console.log("Finished cleanup");
} catch (error) {
logger.error(errorMessages.JOB_QUEUE_DELETE_JOB, {
service: SERVICE_NAME,
errorMsg: error.message,
});
}
process.exit(0);
};
process.on("SIGUSR2", cleanup);
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
};
startApp().catch((error) => {
console.log(error);
console.log(error);
});

View File

@@ -1,5 +1,5 @@
const logger = require("../utils/logger");
const { errorMessages } = require("../utils/messages");
import logger from "../utils/logger.js";
import { errorMessages } from "../utils/messages.js";
const handleErrors = (error, req, res, next) => {
const status = error.status || 500;
@@ -12,4 +12,4 @@ const handleErrors = (error, req, res, next) => {
res.status(status).json({ success: false, msg: message });
};
module.exports = { handleErrors };
export { handleErrors };

View File

@@ -1,7 +1,7 @@
const jwt = require("jsonwebtoken");
import jwt from "jsonwebtoken";
const TOKEN_PREFIX = "Bearer ";
const SERVICE_NAME = "allowedRoles";
const { errorMessages } = require("../utils/messages");
import { errorMessages } from "../utils/messages.js";
const isAllowed = (allowedRoles) => {
return (req, res, next) => {
@@ -52,4 +52,4 @@ const isAllowed = (allowedRoles) => {
};
};
module.exports = { isAllowed };
export { isAllowed };

View File

@@ -1,8 +1,9 @@
const jwt = require("jsonwebtoken");
import jwt from "jsonwebtoken";
import { errorMessages } from "../utils/messages.js";
import { handleError } from "../controllers/controllerUtils.js";
const SERVICE_NAME = "verifyJWT";
const TOKEN_PREFIX = "Bearer ";
const { errorMessages } = require("../utils/messages");
const { handleError } = require("../controllers/controllerUtils");
/**
* Verifies the JWT token
@@ -91,4 +92,5 @@ function handleExpiredJwtToken(req, res, next) {
});
}
module.exports = { verifyJWT };
export { verifyJWT };

View File

@@ -1,6 +1,6 @@
const logger = require("../utils/logger");
import logger from "../utils/logger.js";
import { errorMessages } from "../utils/messages.js";
const SERVICE_NAME = "verifyOwnership";
const { errorMessages } = require("../utils/messages");
const verifyOwnership = (Model, paramName) => {
return async (req, res, next) => {
@@ -46,4 +46,4 @@ const verifyOwnership = (Model, paramName) => {
};
};
module.exports = { verifyOwnership };
export { verifyOwnership };

14830
Server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,8 +3,9 @@
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "nyc mocha",
"test": "c8 mocha",
"dev": "nodemon index.js"
},
"keywords": [],
@@ -15,7 +16,6 @@
"axios": "^1.7.2",
"bcrypt": "^5.1.1",
"bullmq": "5.7.15",
"chai": "5.1.1",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
@@ -25,21 +25,21 @@
"jsonwebtoken": "9.0.2",
"mailersend": "^2.2.0",
"mjml": "^5.0.0-alpha.4",
"mocha": "10.7.3",
"mongoose": "^8.3.3",
"multer": "1.4.5-lts.1",
"nodemailer": "^6.9.14",
"ping": "0.4.4",
"sharp": "0.33.4",
"ssl-checker": "2.0.10",
"swagger-ui-express": "5.0.1",
"winston": "^3.13.0"
},
"devDependencies": {
"nodemon": "3.1.0",
"prettier": "^3.3.3",
"nyc": "17.1.0",
"proxyquire": "2.1.3",
"chai": "5.1.1",
"mocha": "10.7.3",
"c8": "10.1.2",
"esm": "3.2.25",
"nodemon": "3.1.0",
"sinon": "19.0.2"
}
}

View File

@@ -1,12 +1,14 @@
const router = require("express").Router();
const { verifyJWT } = require("../middleware/verifyJWT");
const { verifyOwnership } = require("../middleware/verifyOwnership");
const { isAllowed } = require("../middleware/isAllowed");
const multer = require("multer");
const upload = multer();
const User = require("../db/models/User");
import { Router } from "express";
import { verifyJWT } from "../middleware/verifyJWT.js";
import { verifyOwnership } from "../middleware/verifyOwnership.js";
import { isAllowed } from "../middleware/isAllowed.js";
import multer from "multer";
import User from "../db/models/User.js";
const {
const router = Router();
const upload = multer();
import {
registerUser,
loginUser,
editUser,
@@ -16,7 +18,7 @@ const {
checkSuperadminExists,
getAllUsers,
deleteUser,
} = require("../controllers/authController");
} from "../controllers/authController.js";
//Auth routes
router.post("/register", upload.single("profileImage"), registerUser);
@@ -31,4 +33,4 @@ router.post("/recovery/request", requestRecovery);
router.post("/recovery/validate", validateRecovery);
router.post("/recovery/reset/", resetPassword);
module.exports = router;
export default router;

View File

@@ -1,33 +1,34 @@
const router = require("express").Router();
const checkController = require("../controllers/checkController");
const { verifyOwnership } = require("../middleware/verifyOwnership");
const { isAllowed } = require("../middleware/isAllowed");
const Monitor = require("../db/models/Monitor");
import { Router } from "express";
import {
createCheck,
getChecks,
deleteChecks,
getTeamChecks,
deleteChecksByTeamId,
updateChecksTTL,
} from "../controllers/checkController.js";
import { verifyOwnership } from "../middleware/verifyOwnership.js";
import { isAllowed } from "../middleware/isAllowed.js";
import Monitor from "../db/models/Monitor.js";
router.get("/:monitorId", checkController.getChecks);
router.post(
"/:monitorId",
verifyOwnership(Monitor, "monitorId"),
checkController.createCheck
);
const router = Router();
router.get("/:monitorId", getChecks);
router.post("/:monitorId", verifyOwnership(Monitor, "monitorId"), createCheck);
router.delete(
"/:monitorId",
verifyOwnership(Monitor, "monitorId"),
checkController.deleteChecks
deleteChecks
);
router.get("/team/:teamId", checkController.getTeamChecks);
router.get("/team/:teamId", getTeamChecks);
router.delete(
"/team/:teamId",
isAllowed(["admin", "superadmin"]),
checkController.deleteChecksByTeamId
deleteChecksByTeamId
);
router.put(
"/team/ttl",
isAllowed(["admin", "superadmin"]),
checkController.updateChecksTTL
);
router.put("/team/ttl", isAllowed(["admin", "superadmin"]), updateChecksTTL);
module.exports = router;
export default router;

View File

@@ -1,11 +1,12 @@
const router = require("express").Router();
const { verifyJWT } = require("../middleware/verifyJWT");
const { isAllowed } = require("../middleware/isAllowed");
const {
import { Router } from "express";
import { verifyJWT } from "../middleware/verifyJWT.js";
import { isAllowed } from "../middleware/isAllowed.js";
import {
issueInvitation,
inviteVerifyController,
} = require("../controllers/inviteController");
} from "../controllers/inviteController.js";
const router = Router();
router.post(
"/",
@@ -15,4 +16,4 @@ router.post(
);
router.post("/verify", issueInvitation);
module.exports = router;
export default router;

View File

@@ -1,22 +1,31 @@
const router = require("express").Router();
const maintenanceWindowController = require("../controllers/maintenanceWindowController");
const { verifyOwnership } = require("../middleware/verifyOwnership");
const Monitor = require("../db/models/Monitor");
import { Router } from "express";
import {
createMaintenanceWindows,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindow,
editMaintenanceWindow,
} from "../controllers/maintenanceWindowController.js";
import { verifyOwnership } from "../middleware/verifyOwnership.js";
import Monitor from "../db/models/Monitor.js";
router.post("/", maintenanceWindowController.createMaintenanceWindows);
const router = Router();
router.post("/", createMaintenanceWindows);
router.get(
"/monitor/:monitorId",
verifyOwnership(Monitor, "monitorId"),
maintenanceWindowController.getMaintenanceWindowsByMonitorId
getMaintenanceWindowsByMonitorId
);
router.get("/team/", maintenanceWindowController.getMaintenanceWindowsByTeamId);
router.get("/team/", getMaintenanceWindowsByTeamId);
router.get("/:id", maintenanceWindowController.getMaintenanceWindowById);
router.get("/:id", getMaintenanceWindowById);
router.put("/:id", maintenanceWindowController.editMaintenanceWindow);
router.put("/:id", editMaintenanceWindow);
router.delete("/:id", maintenanceWindowController.deleteMaintenanceWindow);
router.delete("/:id", deleteMaintenanceWindow);
module.exports = router;
export default router;

View File

@@ -1,51 +1,42 @@
const router = require("express").Router();
const monitorController = require("../controllers/monitorController");
const { isAllowed } = require("../middleware/isAllowed");
import { Router } from "express";
import {
getAllMonitors,
getMonitorStatsById,
getMonitorCertificate,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
editMonitor,
pauseMonitor,
addDemoMonitors,
} from "../controllers/monitorController.js";
import { isAllowed } from "../middleware/isAllowed.js";
import { fetchMonitorCertificate } from "../controllers/controllerUtils.js";
router.get("/", monitorController.getAllMonitors);
router.get("/stats/:monitorId", monitorController.getMonitorStatsById);
router.get("/certificate/:monitorId", monitorController.getMonitorCertificate);
router.get("/:monitorId", monitorController.getMonitorById);
router.get(
"/team/summary/:teamId",
monitorController.getMonitorsAndSummaryByTeamId
);
router.get("/team/:teamId", monitorController.getMonitorsByTeamId);
const router = Router();
router.post(
"/",
isAllowed(["admin", "superadmin"]),
monitorController.createMonitor
);
router.get("/", getAllMonitors);
router.get("/stats/:monitorId", getMonitorStatsById);
router.get("/certificate/:monitorId", (req, res, next) => {
getMonitorCertificate(req, res, next, fetchMonitorCertificate);
});
router.get("/:monitorId", getMonitorById);
router.get("/team/summary/:teamId", getMonitorsAndSummaryByTeamId);
router.get("/team/:teamId", getMonitorsByTeamId);
router.delete(
"/:monitorId",
isAllowed(["admin", "superadmin"]),
monitorController.deleteMonitor
);
router.post("/", isAllowed(["admin", "superadmin"]), createMonitor);
router.put(
"/:monitorId",
isAllowed(["admin", "superadmin"]),
monitorController.editMonitor
);
router.delete("/:monitorId", isAllowed(["admin", "superadmin"]), deleteMonitor);
router.delete(
"/",
isAllowed(["superadmin"]),
monitorController.deleteAllMonitors
);
router.put("/:monitorId", isAllowed(["admin", "superadmin"]), editMonitor);
router.post(
"/pause/:monitorId",
isAllowed(["admin", "superadmin"]),
monitorController.pauseMonitor
);
router.delete("/", isAllowed(["superadmin"]), deleteAllMonitors);
router.post(
"/demo",
isAllowed(["admin", "superadmin"]),
monitorController.addDemoMonitors
);
router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), pauseMonitor);
module.exports = router;
router.post("/demo", isAllowed(["admin", "superadmin"]), addDemoMonitors);
export default router;

View File

@@ -1,15 +1,21 @@
const router = require("express").Router();
const queueController = require("../controllers/queueController");
import { Router } from "express";
import {
getMetrics,
getJobs,
addJob,
obliterateQueue,
} from "../controllers/queueController.js";
router.get("/metrics", queueController.getMetrics);
const router = Router();
router.get("/metrics", getMetrics);
// Get Jobs
router.get("/jobs", queueController.getJobs);
router.get("/jobs", getJobs);
// Add Job
router.post("/jobs", queueController.addJob);
router.post("/jobs", addJob);
// Obliterate Queue
router.post("/obliterate", queueController.obliterateQueue);
router.post("/obliterate", obliterateQueue);
module.exports = router;
export default router;

View File

@@ -1,13 +1,13 @@
const router = require("express").Router();
const settingsController = require("../controllers/settingsController");
const { isAllowed } = require("../middleware/isAllowed");
const Monitor = require("../db/models/Monitor");
import { Router } from "express";
import {
getAppSettings,
updateAppSettings,
} from "../controllers/settingsController.js";
import { isAllowed } from "../middleware/isAllowed.js";
router.get("/", settingsController.getAppSettings);
router.put(
"/",
isAllowed(["superadmin"]),
settingsController.updateAppSettings
);
const router = Router();
module.exports = router;
router.get("/", getAppSettings);
router.put("/", isAllowed(["superadmin"]), updateAppSettings);
export default router;

View File

@@ -1,10 +1,15 @@
const fs = require("fs");
const path = require("path");
const nodemailer = require("nodemailer");
const { compile } = require("handlebars");
const mjml2html = require("mjml");
import fs from "fs";
import path from "path";
import nodemailer from "nodemailer";
import pkg from "handlebars";
const { compile } = pkg;
import mjml2html from "mjml";
import logger from "../utils/logger.js";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const SERVICE_NAME = "EmailService";
const logger = require("../utils/logger");
/**
* Represents an email service that can load templates, build, and send emails.
@@ -134,5 +139,4 @@ class EmailService {
}
};
}
module.exports = EmailService;
export default EmailService;

View File

@@ -1,9 +1,9 @@
const { Queue, Worker, Job } = require("bullmq");
import { Queue, Worker, Job } from "bullmq";
const QUEUE_NAME = "monitors";
const JOBS_PER_WORKER = 5;
const logger = require("../utils/logger");
const { errorMessages, successMessages } = require("../utils/messages");
import logger from "../utils/logger.js";
import { errorMessages, successMessages } from "../utils/messages.js";
const SERVICE_NAME = "JobQueue";
/**
* JobQueue
@@ -360,4 +360,4 @@ class JobQueue {
}
}
module.exports = JobQueue;
export default JobQueue;

View File

@@ -1,8 +1,8 @@
const axios = require("axios");
const ping = require("ping");
const logger = require("../utils/logger");
const http = require("http");
const { errorMessages, successMessages } = require("../utils/messages");
import axios from "axios";
import ping from "ping";
import logger from "../utils/logger.js";
import http from "http";
import { errorMessages, successMessages } from "../utils/messages.js";
/**
* NetworkService
@@ -329,4 +329,4 @@ class NetworkService {
}
}
module.exports = NetworkService;
export default NetworkService;

View File

@@ -1,5 +1,4 @@
const { env } = require("process");
const AppSettings = require("../db/models/AppSettings");
import AppSettings from "../db/models/AppSettings.js";
const SERVICE_NAME = "SettingsService";
const envConfig = {
logLevel: undefined,
@@ -83,4 +82,4 @@ class SettingsService {
}
}
module.exports = SettingsService;
export default SettingsService;

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,16 @@
const {
import {
createCheck,
getChecks,
getTeamChecks,
deleteChecks,
deleteChecksByTeamId,
updateChecksTTL,
} = require("../../controllers/checkController");
const jwt = require("jsonwebtoken");
const { errorMessages, successMessages } = require("../../utils/messages");
const sinon = require("sinon");
} from "../../controllers/checkController.js";
import jwt from "jsonwebtoken";
import { errorMessages, successMessages } from "../../utils/messages.js";
import sinon from "sinon";
describe("Check Controller - createCheck", () => {
let req, res, next, handleError;
beforeEach(() => {
req = {
params: {},
@@ -88,6 +88,7 @@ describe("Check Controller - createCheck", () => {
});
describe("Check Controller - getChecks", () => {
let req, res, next;
beforeEach(() => {
req = {
params: {},
@@ -143,6 +144,7 @@ describe("Check Controller - getChecks", () => {
});
describe("Check Controller - getTeamChecks", () => {
let req, res, next;
beforeEach(() => {
req = {
params: {},
@@ -197,6 +199,7 @@ describe("Check Controller - getTeamChecks", () => {
});
describe("Check Controller - deleteChecks", () => {
let req, res, next;
beforeEach(() => {
req = {
params: {},
@@ -248,6 +251,7 @@ describe("Check Controller - deleteChecks", () => {
});
describe("Check Controller - deleteChecksByTeamId", () => {
let req, res, next;
beforeEach(() => {
req = {
params: {},
@@ -301,6 +305,7 @@ describe("Check Controller - deleteChecksByTeamId", () => {
});
describe("Check Controller - updateCheckTTL", () => {
let stub, req, res, next;
beforeEach(() => {
stub = sinon.stub(jwt, "verify").callsFake(() => {
return { teamId: "123" };

View File

@@ -1,105 +1,210 @@
const {
handleValidationError,
handleError,
} = require("../../controllers/controllerUtils");
import sinon from "sinon";
import {
handleValidationError,
handleError,
fetchMonitorCertificate,
} from "../../controllers/controllerUtils.js";
import { expect } from "chai";
describe("controllerUtils - handleValidationError", () => {
it("should set status to 422", () => {
const error = {};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.status).to.equal(422);
});
it("should set status to 422", () => {
const error = {};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.status).to.equal(422);
});
it("should set service to the provided serviceName", () => {
const error = {};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.service).to.equal(serviceName);
});
it("should set service to the provided serviceName", () => {
const error = {};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.service).to.equal(serviceName);
});
it("should set message to error.details[0].message if present", () => {
const error = {
details: [{ message: "Detail message" }],
};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.message).to.equal("Detail message");
});
it("should set message to error.details[0].message if present", () => {
const error = {
details: [{ message: "Detail message" }],
};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.message).to.equal("Detail message");
});
it("should set message to error.message if error.details is not present", () => {
const error = {
message: "Error message",
};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.message).to.equal("Error message");
});
it("should set message to error.message if error.details is not present", () => {
const error = {
message: "Error message",
};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.message).to.equal("Error message");
});
it('should set message to "Validation Error" if neither error.details nor error.message is present', () => {
const error = {};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.message).to.equal("Validation Error");
});
it('should set message to "Validation Error" if neither error.details nor error.message is present', () => {
const error = {};
const serviceName = "TestService";
const result = handleValidationError(error, serviceName);
expect(result.message).to.equal("Validation Error");
});
});
describe("handleError", () => {
it("should set stats to the provided status if error.code is undefined", () => {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
const status = 400;
const result = handleError(error, serviceName, method, status);
expect(result.status).to.equal(status);
});
describe("controllerUtils - handleError", () => {
it("should set stats to the provided status if error.code is undefined", () => {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
const status = 400;
const result = handleError(error, serviceName, method, status);
expect(result.status).to.equal(status);
});
it("should not overwrite error.code if it is already defined", () => {
const error = { status: 404 };
const serviceName = "TestService";
const method = "testMethod";
const status = 400;
const result = handleError(error, serviceName, method, status);
expect(result.status).to.equal(404);
});
it("should not overwrite error.code if it is already defined", () => {
const error = { status: 404 };
const serviceName = "TestService";
const method = "testMethod";
const status = 400;
const result = handleError(error, serviceName, method, status);
expect(result.status).to.equal(404);
});
it("should set service to the provided serviceName if error.service is undefined", () => {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
const result = handleError(error, serviceName, method);
expect(result.service).to.equal(serviceName);
});
it("should set service to the provided serviceName if error.service is undefined", () => {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
const result = handleError(error, serviceName, method);
expect(result.service).to.equal(serviceName);
});
it("should not overwrite error.service if it is already defined", () => {
const error = { service: "ExistingService" };
const serviceName = "TestService";
const method = "testMethod";
const result = handleError(error, serviceName, method);
expect(result.service).to.equal("ExistingService");
});
it("should not overwrite error.service if it is already defined", () => {
const error = { service: "ExistingService" };
const serviceName = "TestService";
const method = "testMethod";
const result = handleError(error, serviceName, method);
expect(result.service).to.equal("ExistingService");
});
it("should set method to the provided method if error.method is undefined", () => {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
const result = handleError(error, serviceName, method);
expect(result.method).to.equal(method);
});
it("should set method to the provided method if error.method is undefined", () => {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
const result = handleError(error, serviceName, method);
expect(result.method).to.equal(method);
});
it("should not overwrite error.method if it is already defined", () => {
const error = { method: "existingMethod" };
const serviceName = "TestService";
const method = "testMethod";
const result = handleError(error, serviceName, method);
expect(result.method).to.equal("existingMethod");
});
it("should not overwrite error.method if it is already defined", () => {
const error = { method: "existingMethod" };
const serviceName = "TestService";
const method = "testMethod";
const result = handleError(error, serviceName, method);
expect(result.method).to.equal("existingMethod");
});
it("should set code to 500 if error.code is undefined and no code is provided", () => {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
const result = handleError(error, serviceName, method);
expect(result.status).to.equal(500);
});
it("should set code to 500 if error.code is undefined and no code is provided", () => {
const error = {};
const serviceName = "TestService";
const method = "testMethod";
const result = handleError(error, serviceName, method);
expect(result.status).to.equal(500);
});
});
describe("controllerUtils - fetchMonitorCertificate", () => {
const originalTls = {
connect: sinon.stub().callsFake((options, callback) => {
// Create socket stub with sinon stubs for all methods
socket = {
getPeerX509Certificate: sinon.stub().returns({
subject: "CN=fake-cert",
validTo: "Dec 31 23:59:59 2023 GMT",
}),
end: sinon.stub(),
on: sinon.stub(),
};
// Use process.nextTick to ensure async behavior
process.nextTick(() => {
callback.call(socket); // Ensure correct 'this' binding
});
return socket;
}),
};
let tls, monitor, socket;
beforeEach(() => {
monitor = { url: "https://www.google.com" };
tls = {
connect: sinon.stub().callsFake((options, callback) => {
// Create socket stub with sinon stubs for all methods
socket = {
getPeerX509Certificate: sinon.stub().returns({
subject: "CN=fake-cert",
validTo: "Dec 31 23:59:59 2023 GMT",
}),
end: sinon.stub(),
on: sinon.stub(),
};
// Use process.nextTick to ensure async behavior
process.nextTick(() => {
callback.call(socket); // Ensure correct 'this' binding
});
return socket;
}),
};
});
afterEach(() => {
tls = { ...originalTls };
sinon.restore();
});
it("should resolve with the certificate when the connection is successful", async () => {
const certificate = await fetchMonitorCertificate(tls, monitor);
expect(certificate.validTo).to.equal("Dec 31 23:59:59 2023 GMT");
expect(socket.end.calledOnce).to.be.true;
});
it("should reject with an error when the connection fails", async () => {
tls.connect = sinon.stub().throws(new Error("Connection error"));
try {
await fetchMonitorCertificate(tls, monitor);
} catch (error) {
expect(error.message).to.equal("Connection error");
}
});
it("should reject with an error if monitorURL is invalid", async () => {
monitor.url = "invalid-url";
try {
await fetchMonitorCertificate(tls, monitor);
} catch (error) {
expect(error.message).to.equal("Invalid URL");
}
});
it("should do a thing", async () => {
tls = {
connect: sinon.stub().callsFake((options, callback) => {
// Create socket stub with sinon stubs for all methods
socket = {
getPeerX509Certificate: sinon.stub().throws(new Error("Certificate error")),
end: sinon.stub(),
on: sinon.stub(),
};
// Use process.nextTick to ensure async behavior
process.nextTick(() => {
callback.call(socket); // Ensure correct 'this' binding
});
return socket;
}),
};
try {
await fetchMonitorCertificate(tls, monitor);
} catch (error) {
expect(error.message).to.equal("Certificate error");
}
});
});

View File

@@ -1,12 +1,12 @@
const {
import {
issueInvitation,
inviteVerifyController,
} = require("../../controllers/inviteController");
const jwt = require("jsonwebtoken");
const { errorMessages, successMessages } = require("../../utils/messages");
const sinon = require("sinon");
const joi = require("joi");
} from "../../controllers/inviteController.js";
import jwt from "jsonwebtoken";
import sinon from "sinon";
import joi from "joi";
describe("inviteController - issueInvitation", () => {
let req, res, next, stub;
beforeEach(() => {
req = {
headers: { authorization: "Bearer token" },
@@ -154,6 +154,7 @@ describe("inviteController - issueInvitation", () => {
});
describe("inviteController - inviteVerifyController", () => {
let req, res, next;
beforeEach(() => {
req = {
body: { token: "token" },

View File

@@ -1,17 +1,18 @@
const {
import {
createMaintenanceWindows,
getMaintenanceWindowById,
getMaintenanceWindowsByTeamId,
getMaintenanceWindowsByMonitorId,
deleteMaintenanceWindow,
editMaintenanceWindow,
} = require("../../controllers/maintenanceWindowController");
} from "../../controllers/maintenanceWindowController.js";
const jwt = require("jsonwebtoken");
const { errorMessages, successMessages } = require("../../utils/messages");
const sinon = require("sinon");
import jwt from "jsonwebtoken";
import { successMessages } from "../../utils/messages.js";
import sinon from "sinon";
describe("maintenanceWindowController - createMaintenanceWindows", () => {
let req, res, next, stub;
beforeEach(() => {
req = {
body: {
@@ -103,6 +104,7 @@ describe("maintenanceWindowController - createMaintenanceWindows", () => {
});
describe("maintenanceWindowController - getMaintenanceWindowById", () => {
let req, res, next;
beforeEach(() => {
req = {
body: {},
@@ -154,6 +156,7 @@ describe("maintenanceWindowController - getMaintenanceWindowById", () => {
});
describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => {
let req, res, next, stub;
beforeEach(() => {
req = {
body: {},
@@ -220,6 +223,7 @@ describe("maintenanceWindowController - getMaintenanceWindowsByTeamId", () => {
});
describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", () => {
let req, res, next;
beforeEach(() => {
req = {
body: {},
@@ -283,6 +287,7 @@ describe("maintenanceWindowController - getMaintenanceWindowsByMonitorId", () =>
});
describe("maintenanceWindowController - deleteMaintenanceWindow", () => {
let req, res, next;
beforeEach(() => {
req = {
body: {},
@@ -338,6 +343,7 @@ describe("maintenanceWindowController - deleteMaintenanceWindow", () => {
});
describe("maintenanceWindowController - editMaintenanceWindow", () => {
let req, res, next;
beforeEach(() => {
req = {
body: {

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +1,15 @@
const { afterEach } = require("node:test");
const {
import { afterEach } from "node:test";
import {
getMetrics,
getJobs,
addJob,
obliterateQueue,
} = require("../../controllers/queueController");
const SERVICE_NAME = "JobQueueController";
const { errorMessages, successMessages } = require("../../utils/messages");
const sinon = require("sinon");
} from "../../controllers/queueController.js";
import { successMessages } from "../../utils/messages.js";
import sinon from "sinon";
describe("Queue Controller - getMetrics", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
@@ -26,7 +25,6 @@ describe("Queue Controller - getMetrics", () => {
json: sinon.stub(),
};
next = sinon.stub();
handleError = sinon.stub();
});
afterEach(() => {
sinon.restore();
@@ -52,6 +50,7 @@ describe("Queue Controller - getMetrics", () => {
});
describe("Queue Controller - getJobs", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
@@ -67,7 +66,6 @@ describe("Queue Controller - getJobs", () => {
json: sinon.stub(),
};
next = sinon.stub();
handleError = sinon.stub();
});
afterEach(() => {
sinon.restore();
@@ -92,6 +90,7 @@ describe("Queue Controller - getJobs", () => {
});
describe("Queue Controller - addJob", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
@@ -107,7 +106,6 @@ describe("Queue Controller - addJob", () => {
json: sinon.stub(),
};
next = sinon.stub();
handleError = sinon.stub();
});
afterEach(() => {
sinon.restore();
@@ -130,6 +128,7 @@ describe("Queue Controller - addJob", () => {
});
describe("Queue Controller - obliterateQueue", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
@@ -145,7 +144,6 @@ describe("Queue Controller - obliterateQueue", () => {
json: sinon.stub(),
};
next = sinon.stub();
handleError = sinon.stub();
});
afterEach(() => {
sinon.restore();

View File

@@ -1,13 +1,14 @@
const { afterEach } = require("node:test");
const {
import { afterEach } from "node:test";
import {
getAppSettings,
updateAppSettings,
} = require("../../controllers/settingsController");
} from "../../controllers/settingsController.js";
const { errorMessages, successMessages } = require("../../utils/messages");
const sinon = require("sinon");
import { successMessages } from "../../utils/messages.js";
import sinon from "sinon";
describe("Settings Controller - getAppSettings", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
@@ -23,7 +24,6 @@ describe("Settings Controller - getAppSettings", () => {
json: sinon.stub(),
};
next = sinon.stub();
handleError = sinon.stub();
});
afterEach(() => {
sinon.restore();
@@ -49,6 +49,7 @@ describe("Settings Controller - getAppSettings", () => {
});
describe("Settings Controller - updateAppSettings", () => {
let req, res, next;
beforeEach(() => {
req = {
headers: {},
@@ -66,7 +67,6 @@ describe("Settings Controller - updateAppSettings", () => {
json: sinon.stub(),
};
next = sinon.stub();
handleError = sinon.stub();
});
afterEach(() => {
sinon.restore();

View File

@@ -0,0 +1,68 @@
import { NormalizeData, calculatePercentile } from "../../utils/dataUtils.js";
import sinon from "sinon";
describe("NormalizeData", () => {
it("should normalize response times when checks length is greater than 1", () => {
const checks = [
{ responseTime: 20, _doc: { id: 1 } },
{ responseTime: 40, _doc: { id: 2 } },
{ responseTime: 60, _doc: { id: 3 } },
];
const rangeMin = 1;
const rangeMax = 100;
const result = NormalizeData(checks, rangeMin, rangeMax);
expect(result).to.be.an("array");
expect(result).to.have.lengthOf(3);
result.forEach((check) => {
expect(check).to.have.property("responseTime").that.is.a("number");
expect(check).to.have.property("originalResponseTime").that.is.a("number");
});
});
it("should return checks with original response times when checks length is 1", () => {
const checks = [{ responseTime: 20, _doc: { id: 1 } }];
const rangeMin = 1;
const rangeMax = 100;
const result = NormalizeData(checks, rangeMin, rangeMax);
expect(result).to.be.an("array");
expect(result).to.have.lengthOf(1);
expect(result[0]).to.have.property("originalResponseTime", 20);
});
it("should handle edge cases with extreme response times", () => {
const checks = [
{ responseTime: 5, _doc: { id: 1 } },
{ responseTime: 95, _doc: { id: 2 } },
];
const rangeMin = 1;
const rangeMax = 100;
const result = NormalizeData(checks, rangeMin, rangeMax);
expect(result).to.be.an("array");
expect(result).to.have.lengthOf(2);
expect(result[0]).to.have.property("responseTime").that.is.at.least(rangeMin);
expect(result[1]).to.have.property("responseTime").that.is.at.most(rangeMax);
});
});
describe("calculatePercentile", () => {
it("should return the lower value when upper is greater than or equal to the length of the sorted array", () => {
const checks = [
{ responseTime: 10 },
{ responseTime: 20 },
{ responseTime: 30 },
{ responseTime: 40 },
{ responseTime: 50 },
];
const percentile = 100;
const result = calculatePercentile(checks, percentile);
const expected = 50;
console.log(result);
expect(result).to.equal(expected);
});
});

View File

@@ -0,0 +1,57 @@
import { expect } from "chai";
import sinon from "sinon";
import sharp from "sharp";
import { GenerateAvatarImage } from "../../utils/imageProcessing.js";
describe("imageProcessing - GenerateAvatarImage", () => {
it("should resize the image to 64x64 and return a base64 string", async () => {
const file = {
buffer: Buffer.from("test image buffer"),
};
// Stub the sharp function
const toBufferStub = sinon.stub().resolves(Buffer.from("resized image buffer"));
const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub });
const sharpStub = sinon
.stub(sharp.prototype, "resize")
.returns({ toBuffer: toBufferStub });
const result = await GenerateAvatarImage(file);
// Verify the result
const expected = Buffer.from("resized image buffer").toString("base64");
expect(result).to.equal(expected);
// Verify that the sharp function was called with the correct arguments
expect(sharpStub.calledOnceWith({ width: 64, height: 64, fit: "cover" })).to.be.true;
expect(toBufferStub.calledOnce).to.be.true;
// Restore the stubbed functions
sharpStub.restore();
});
it("should throw an error if resizing fails", async () => {
const file = {
buffer: Buffer.from("test image buffer"),
};
// Stub the sharp function to throw an error
const toBufferStub = sinon.stub().rejects(new Error("Resizing failed"));
const resizeStub = sinon.stub().returns({ toBuffer: toBufferStub });
const sharpStub = sinon
.stub(sharp.prototype, "resize")
.returns({ toBuffer: toBufferStub });
try {
await GenerateAvatarImage(file);
// If no error is thrown, fail the test
expect.fail("Expected error to be thrown");
} catch (error) {
// Verify that the error message is correct
expect(error.message).to.equal("Resizing failed");
}
// Restore the stubbed functions
sharpStub.restore();
});
});

View File

@@ -0,0 +1,29 @@
import { errorMessages, successMessages } from "../../utils/messages.js";
describe("Messages", () => {
describe("messages - errorMessages", () => {
it("should have a DB_FIND_MONITOR_BY_ID function", () => {
const monitorId = "12345";
expect(errorMessages.DB_FIND_MONITOR_BY_ID(monitorId)).to.equal(
`Monitor with id ${monitorId} not found`
);
});
it("should have a DB_DELETE_CHECKS function", () => {
const monitorId = "12345";
expect(errorMessages.DB_DELETE_CHECKS(monitorId)).to.equal(
`No checks found for monitor with id ${monitorId}`
);
});
});
describe("messages - successMessages", () => {
it("should have a MONITOR_GET_BY_USER_ID function", () => {
const userId = "12345";
expect(successMessages.MONITOR_GET_BY_USER_ID(userId)).to.equal(
`Got monitor for ${userId} successfully"`
);
});
// Add more tests for other success messages as needed
});
});

View File

@@ -0,0 +1,50 @@
import { ParseBoolean, getTokenFromHeaders } from "../../utils/utils.js";
describe("utils - ParseBoolean", () => {
it("should return true", () => {
const result = ParseBoolean("true");
expect(result).to.be.true;
});
it("should return false", () => {
const result = ParseBoolean("false");
expect(result).to.be.false;
});
it("should return false", () => {
const result = ParseBoolean(null);
expect(result).to.be.false;
});
it("should return false", () => {
const result = ParseBoolean(undefined);
expect(result).to.be.false;
});
});
describe("utils - getTokenFromHeaders", () => {
it("should throw an error if authorization header is missing", () => {
const headers = {};
expect(() => getTokenFromHeaders(headers)).to.throw("No auth headers");
});
it("should throw an error if authorization header does not start with Bearer", () => {
const headers = { authorization: "Basic abcdef" };
expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
});
it("should return the token if authorization header is correctly formatted", () => {
const headers = { authorization: "Bearer abcdef" };
expect(getTokenFromHeaders(headers)).to.equal("abcdef");
});
it("should throw an error if authorization header has more than two parts", () => {
const headers = { authorization: "Bearer abc def" };
expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
});
it("should throw an error if authorization header has less than two parts", () => {
const headers = { authorization: "Bearer" };
expect(() => getTokenFromHeaders(headers)).to.throw("Invalid auth headers");
});
});

View File

@@ -1,50 +1,44 @@
const calculatePercentile = (arr, percentile) => {
const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime);
const index = (percentile / 100) * (sorted.length - 1);
const lower = Math.floor(index);
const upper = lower + 1;
const weight = index % 1;
if (upper >= sorted.length) return sorted[lower].responseTime;
return (
sorted[lower].responseTime * (1 - weight) +
sorted[upper].responseTime * weight
);
const sorted = arr.slice().sort((a, b) => a.responseTime - b.responseTime);
const index = (percentile / 100) * (sorted.length - 1);
const lower = Math.floor(index);
const upper = lower + 1;
const weight = index % 1;
if (upper >= sorted.length) return sorted[lower].responseTime;
return sorted[lower].responseTime * (1 - weight) + sorted[upper].responseTime * weight;
};
const NormalizeData = (checks, rangeMin, rangeMax) => {
if (checks.length > 1) {
// Get the 5th and 95th percentile
const min = calculatePercentile(checks, 0);
const max = calculatePercentile(checks, 95);
if (checks.length > 1) {
// Get the 5th and 95th percentile
const min = calculatePercentile(checks, 0);
const max = calculatePercentile(checks, 95);
const normalizedChecks = checks.map((check) => {
const originalResponseTime = check.responseTime;
// Normalize the response time between 1 and 100
let normalizedResponseTime =
rangeMin +
((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min);
const normalizedChecks = checks.map((check) => {
const originalResponseTime = check.responseTime;
// Normalize the response time between 1 and 100
let normalizedResponseTime =
rangeMin + ((check.responseTime - min) * (rangeMax - rangeMin)) / (max - min);
// Put a floor on the response times so we don't have extreme outliers
// Better visuals
normalizedResponseTime = Math.max(
rangeMin,
Math.min(rangeMax, normalizedResponseTime)
);
return {
...check._doc,
responseTime: normalizedResponseTime,
originalResponseTime: originalResponseTime,
};
});
// Put a floor on the response times so we don't have extreme outliers
// Better visuals
normalizedResponseTime = Math.max(
rangeMin,
Math.min(rangeMax, normalizedResponseTime)
);
return {
...check._doc,
responseTime: normalizedResponseTime,
originalResponseTime: originalResponseTime,
};
});
return normalizedChecks;
} else {
return checks.map((check) => {
return { ...check._doc, originalResponseTime: check.responseTime };
});
}
return normalizedChecks;
} else {
return checks.map((check) => {
return { ...check._doc, originalResponseTime: check.responseTime };
});
}
};
module.exports = {
NormalizeData,
};
export { calculatePercentile, NormalizeData };

View File

@@ -1,4 +1,4 @@
const sharp = require("sharp");
import sharp from "sharp";
/**
* Generates a 64 * 64 pixel image from a given image
* @param {} file
@@ -22,6 +22,4 @@ const GenerateAvatarImage = async (file) => {
}
};
module.exports = {
GenerateAvatarImage,
};
export { GenerateAvatarImage };

View File

@@ -1,8 +1,8 @@
const winston = require('winston');
import winston from "winston";
/**
* @module
* @example
* logger.info("Registered a new user!")
* logger.info("Registered a new user!")
* logger.warn("User not found!")
* logger.error("Cannot save")
* @example
@@ -12,15 +12,15 @@ const winston = require('winston');
* logger.error("User not found!",{"service":"Auth"})
*/
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
level: "info",
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' }),
new winston.transports.File({ filename: "app.log" }),
],
});
module.exports = logger;
export default logger;

View File

@@ -121,7 +121,4 @@ const successMessages = {
UPDATE_APP_SETTINGS: "Updated app settings successfully",
};
module.exports = {
errorMessages,
successMessages,
};
export { errorMessages, successMessages };

View File

@@ -4,36 +4,32 @@
* @returns {boolean}
*/
const ParseBoolean = (value) => {
if (value === true || value === "true") {
return true;
} else if (
value === false ||
value === "false" ||
value === null ||
value === undefined
) {
return false;
}
if (value === true || value === "true") {
return true;
} else if (
value === false ||
value === "false" ||
value === null ||
value === undefined
) {
return false;
}
};
const getTokenFromHeaders = (headers) => {
const authorizationHeader = headers.authorization;
if (!authorizationHeader) throw new Error("No auth headers");
const authorizationHeader = headers.authorization;
if (!authorizationHeader) throw new Error("No auth headers");
const parts = authorizationHeader.split(" ");
if (parts.length !== 2 || parts[0] !== "Bearer")
throw new Error("Invalid auth headers");
const parts = authorizationHeader.split(" ");
if (parts.length !== 2 || parts[0] !== "Bearer")
throw new Error("Invalid auth headers");
return parts[1];
return parts[1];
};
const tokenType = Object.freeze({
ACCESS_TOKEN: "Access token",
REFRESH_TOKEN: "Refresh token"
ACCESS_TOKEN: "Access token",
REFRESH_TOKEN: "Refresh token",
});
module.exports = {
ParseBoolean,
getTokenFromHeaders,
tokenType,
};
export { ParseBoolean, getTokenFromHeaders, tokenType };

View File

@@ -1,7 +1,4 @@
const exp = require("constants");
const joi = require("joi");
const { normalize } = require("path");
const { start } = require("repl");
import joi from "joi";
//****************************************
// Custom Validators
@@ -143,7 +140,7 @@ const inviteBodyValidation = joi.object({
teamId: joi.string().required(),
});
const inviteVerifciationBodyValidation = joi.object({
const inviteVerificationBodyValidation = joi.object({
token: joi.string().required(),
});
@@ -427,7 +424,7 @@ const updateAppSettingsBodyValidation = joi.object({
systemEmailPassword: joi.string().allow(""),
});
module.exports = {
export {
roleValidatior,
loginValidation,
registrationBodyValidation,
@@ -436,7 +433,7 @@ module.exports = {
newPasswordValidation,
inviteRoleValidation,
inviteBodyValidation,
inviteVerificationBodyValidation: inviteVerifciationBodyValidation,
inviteVerificationBodyValidation,
createMonitorBodyValidation,
getMonitorByIdParamValidation,
getMonitorByIdQueryValidation,