Merge branch 'develop' into enhancement/check-if-url-resolves

This commit is contained in:
om-3004 committed 2024-10-17 10:35:41 +05:30
commit c598278c6f
75 files changed
+10649 -11622

No files matched your search

+1 -1
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);
+80
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
@@ -223,6 +223,7 @@ const TeamPanel = () => {
return (
<TabPanel
className="team-panel table-container"
value="team"
sx={{
"& h1": {
+1 -1
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 />
) : (
+1 -1
View File
@@ -46,7 +46,7 @@ const Maintenance = ({ isAdmin }) => {
return (
<Box
className="maintenance"
className="maintenance table-container"
sx={{
':has(> [class*="fallback__"])': {
position: "relative",
+14 -63
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>
);
};
+446 -441
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;
+16 -67
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>
</>
);
};
+1 -1
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 />
) : (
+65 -11
View File
@@ -17,11 +17,12 @@ import PropTypes from "prop-types";
import LoadingButton from "@mui/lab/LoadingButton";
import { setTimezone, setMode } from "../../Features/UI/uiSlice";
import timezones from "../../Utils/timezones.json";
import { useState } from "react";
import { useState,useEffect } from "react";
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;
@@ -37,10 +38,32 @@ const Settings = ({ isAdmin }) => {
const [form, setForm] = useState({
ttl: checkTTL ? (checkTTL / SECONDS_PER_DAY).toString() : 0,
});
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();
//Fetching latest release version from github
useEffect(() => {
const fetchLatestVersion = async () => {
let version="unknown";
try {
const response = await networkService.fetchGithubLatestRelease();
if (!response.status===200) {
throw new Error("Failed to fetch latest version");
}
version=response.data.tag_name;
} catch (error) {
createToast({ body: error.message || "Error fetching latest version" }); // Set error message
} finally{
setVersion(version);
}
};
fetchLatestVersion();
},[]);
const handleChange = (event) => {
const { value, id } = event.target;
const { error } = settingsValidation.validate(
@@ -119,6 +142,8 @@ const Settings = ({ isAdmin }) => {
} catch (error) {
logger.error(error);
createToast({ body: "Failed to clear stats" });
} finally {
setIsOpen(deleteStatsMonitorsInitState)
}
};
@@ -147,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"
@@ -223,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 && (
@@ -263,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 && (
@@ -299,7 +353,7 @@ const Settings = ({ isAdmin }) => {
<Typography component="h1">About</Typography>
</Box>
<Box>
<Typography component="h2">BlueWave Uptime v1.0.0</Typography>
<Typography component="h2">BlueWave Uptime {version}</Typography>
<Typography
sx={{ mt: theme.spacing(2), mb: theme.spacing(6), opacity: 0.6 }}
>
@@ -307,8 +361,8 @@ const Settings = ({ isAdmin }) => {
</Typography>
<Link
level="secondary"
url="https://github.com/bluewave-labs"
label="https://github.com/bluewave-labs"
url="https://github.com/bluewave-labs/bluewave-uptime"
label="https://github.com/bluewave-labs/bluewave-uptime"
/>
</Box>
</ConfigBox>
+14
View File
@@ -858,6 +858,20 @@ class NetworkService {
}
);
}
/**
* ************************************
* Fetcher github latest release version
* ************************************
*
* @async
* @returns {Promise<AxiosResponse>} The response from the axios GET request.
*
*/
async fetchGithubLatestRelease() {
return this.axiosInstance.get('https://api.github.com/repos/bluewave-labs/bluewave-uptime/releases/latest');
}
}
export default NetworkService;
+8
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
};
-8
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
};
+1 -1
View File
@@ -12,4 +12,4 @@ const connectDbAndRunServer = async (app, db) => {
}
};
module.exports = { connectDbAndRunServer };
export { connectDbAndRunServer };
+314 -303
View File
@@ -1,36 +1,47 @@
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 } = 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
* @function
* @param {Object} payload
* @param {tokenType} typeOfToken - Whether to generate refresh token with long TTL or access token with short TTL.
* @param {Object} appSettings
* @returns {String}
* @throws {Error}
*/
const issueToken = (payload, appSettings) => {
try {
const tokenTTL = appSettings.jwtTTL ? appSettings.jwtTTL : "2h";
return jwt.sign(payload, appSettings.jwtSecret, { expiresIn: tokenTTL });
} catch (error) {
throw handleError(error, SERVICE_NAME, "issueToken");
}
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;
return jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL });
} catch (error) {
throw handleError(error, SERVICE_NAME, "issueToken");
}
};
/**
@@ -46,65 +57,65 @@ const issueToken = (payload, 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, 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 },
});
} 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"));
}
};
/**
@@ -120,44 +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, 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 },
});
} 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"));
}
};
/**
@@ -175,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"));
}
};
/**
@@ -238,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"));
}
};
/**
@@ -263,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"));
}
};
/**
@@ -317,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"));
}
};
/**
@@ -349,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, 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"));
}
};
/**
@@ -381,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,
};
+8 -7
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,
+36 -12
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 };
+11 -11
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 };
@@ -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,
+313 -317
View File
@@ -1,26 +1,31 @@
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";
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";
import dns from "dns";
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");
const dns = require('dns');
/**
* Returns all monitors
@@ -32,16 +37,16 @@ const dns = require('dns');
* @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"));
}
};
/**
@@ -54,56 +59,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"));
}
};
/**
@@ -118,30 +122,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"));
}
};
/**
@@ -158,31 +162,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"));
}
};
/**
@@ -198,26 +197,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);
}
};
/**
@@ -232,36 +231,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"));
}
};
/**
@@ -307,37 +306,35 @@ const checkEndpointResolution = 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"));
}
};
/**
@@ -352,34 +349,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"));
}
};
/**
@@ -396,46 +393,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"));
}
};
/**
@@ -450,32 +447,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"));
}
};
/**
@@ -490,37 +487,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,
checkEndpointResolution,
deleteMonitor,
deleteAllMonitors,
editMonitor,
pauseMonitor,
addDemoMonitors,
export {
getAllMonitors,
getMonitorStatsById,
getMonitorCertificate,
getMonitorById,
getMonitorsAndSummaryByTeamId,
getMonitorsByTeamId,
createMonitor,
deleteMonitor,
deleteAllMonitors,
editMonitor,
pauseMonitor,
addDemoMonitors,
};
+4 -8
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 };
+4 -7
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 };
+13 -3
View File
@@ -1,4 +1,4 @@
const mongoose = require("mongoose");
import mongoose from "mongoose";
const AppSettingsSchema = mongoose.Schema(
{
@@ -22,6 +22,11 @@ const AppSettingsSchema = mongoose.Schema(
required: true,
default: "my_secret",
},
refreshTokenSecret: {
type: String,
required: true,
default: "my_refresh_secret",
},
dbType: {
type: String,
required: true,
@@ -44,7 +49,12 @@ const AppSettingsSchema = mongoose.Schema(
jwtTTL: {
type: String,
required: true,
default: "99d",
default: "2h",
},
refreshTokenTTL: {
type: String,
required: true,
default: "7d",
},
pagespeedApiKey: {
type: String,
@@ -78,4 +88,4 @@ const AppSettingsSchema = mongoose.Schema(
}
);
module.exports = mongoose.model("AppSettings", AppSettingsSchema);
export default mongoose.model("AppSettings", AppSettingsSchema);
+4 -5
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);
+2 -3
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);
+2 -3
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);
+3 -3
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);
+2 -4
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);
+2 -3
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);
+2 -2
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);
+2 -4
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);
+68 -68
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);
+21 -24
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,
+5 -5
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,
+5 -9
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 };
@@ -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,
+20 -8
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,
@@ -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,
@@ -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,
+6 -9
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 };
+2 -5
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 };
+7 -7
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,
+140 -138
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);
});
+3 -3
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 };
+3 -3
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 };
+3 -3
View File
@@ -1,7 +1,7 @@
const jwt = require("jsonwebtoken");
import jwt from "jsonwebtoken";
const SERVICE_NAME = "verifyJWT";
const TOKEN_PREFIX = "Bearer ";
const { errorMessages } = require("../utils/messages");
import { errorMessages } from "../utils/messages.js";
/**
* Verifies the JWT token
* @function
@@ -48,4 +48,4 @@ const verifyJWT = (req, res, next) => {
});
};
module.exports = { verifyJWT };
export { verifyJWT };
+3 -3
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 };
+6789 -8041
View File
File diff suppressed because it is too large. Load diff
+7 -7
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"
}
}
+12 -10
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;
+21 -20
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;
+8 -7
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;
+20 -11
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;
+36 -39
View File
@@ -1,26 +1,35 @@
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.get(
"/resolution/url",
isAllowed(["admin", "superadmin"]),
isAllowed(["admin", "superadmin"]),
monitorController.checkEndpointResolution
)
@@ -30,28 +39,16 @@ router.delete(
monitorController.deleteMonitor
);
router.put(
"/:monitorId",
isAllowed(["admin", "superadmin"]),
monitorController.editMonitor
);
router.post("/", isAllowed(["admin", "superadmin"]), createMonitor);
router.delete(
"/",
isAllowed(["superadmin"]),
monitorController.deleteAllMonitors
);
router.delete("/:monitorId", isAllowed(["admin", "superadmin"]), deleteMonitor);
router.post(
"/pause/:monitorId",
isAllowed(["admin", "superadmin"]),
monitorController.pauseMonitor
);
router.put("/:monitorId", isAllowed(["admin", "superadmin"]), editMonitor);
router.post(
"/demo",
isAllowed(["admin", "superadmin"]),
monitorController.addDemoMonitors
);
router.delete("/", isAllowed(["superadmin"]), deleteAllMonitors);
module.exports = router;
router.post("/pause/:monitorId", isAllowed(["admin", "superadmin"]), pauseMonitor);
router.post("/demo", isAllowed(["admin", "superadmin"]), addDemoMonitors);
export default router;
+13 -7
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;
+11 -11
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;
+12 -8
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;
+4 -4
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;
+6 -6
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;
+4 -3
View File
@@ -1,16 +1,17 @@
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,
apiBaseUrl: undefined,
clientHost: process.env.CLIENT_HOST,
jwtSecret: process.env.JWT_SECRET,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET,
dbType: process.env.DB_TYPE,
dbConnectionString: process.env.DB_CONNECTION_STRING,
redisHost: process.env.REDIS_HOST,
redisPort: process.env.REDIS_PORT,
jwtTTL: process.env.TOKEN_TTL,
refreshTokenTTL: process.env.REFRESH_TOKEN_TTL,
pagespeedApiKey: process.env.PAGESPEED_API_KEY,
systemEmailHost: process.env.SYSTEM_EMAIL_HOST,
systemEmailPort: process.env.SYSTEM_EMAIL_PORT,
@@ -81,4 +82,4 @@ class SettingsService {
}
}
module.exports = SettingsService;
export default SettingsService;
File diff suppressed because it is too large. Load diff
@@ -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" };
+195 -90
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");
}
});
});
@@ -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" },
@@ -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
@@ -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();
@@ -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();
+1 -3
View File
@@ -45,6 +45,4 @@ const NormalizeData = (checks, rangeMin, rangeMax) => {
}
};
module.exports = {
NormalizeData,
};
export { NormalizeData };
+2 -4
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 };
+10 -10
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;
+1 -4
View File
@@ -114,7 +114,4 @@ const successMessages = {
UPDATE_APP_SETTINGS: "Updated app settings successfully",
};
module.exports = {
errorMessages,
successMessages,
};
export { errorMessages, successMessages };
+22 -20
View File
@@ -4,30 +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];
};
module.exports = {
ParseBoolean,
getTokenFromHeaders,
};
const tokenType = Object.freeze({
ACCESS_TOKEN: "Access token",
REFRESH_TOKEN: "Refresh token",
});
export { ParseBoolean, getTokenFromHeaders, tokenType };
+4 -7
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,
+1 -1
View File
@@ -79,7 +79,7 @@ Make sure you change the directory to the specified directories, as paths in com
Configure the server with the following environmental variables:
<table><thead><tr><th width="239">ENV Variable Name</th><th width="149">Required/Optional</th><th width="116">Type</th><th>Description</th><th>Accepted Values</th></tr></thead><tbody><tr><td>CLIENT_HOST</td><td>Required</td><td><code>string</code></td><td>Frontend Host</td><td></td></tr><tr><td>JWT_SECRET</td><td>Required</td><td><code>string</code></td><td>JWT secret</td><td></td></tr><tr><td>DB_TYPE</td><td>Optional</td><td><code>string</code></td><td>Specify DB to use</td><td><code>MongoDB | FakeDB</code></td></tr><tr><td>DB_CONNECTION_STRING</td><td>Required</td><td><code>string</code></td><td>Specifies URL for MongoDB Database</td><td></td></tr><tr><td>PORT</td><td>Optional</td><td><code>integer</code></td><td>Specifies Port for Server</td><td></td></tr><tr><td>LOGIN_PAGE_URL</td><td>Required</td><td><code>string</code></td><td>Login url to be used in emailing service</td><td></td></tr><tr><td>REDIS_HOST</td><td>Required</td><td><code>string</code></td><td>Host address for Redis database</td><td></td></tr><tr><td>REDIS_PORT</td><td>Required</td><td><code>integer</code></td><td>Port for Redis database</td><td></td></tr><tr><td>TOKEN_TTL</td><td>Optional</td><td><code>string</code></td><td>Time for token to live</td><td>In vercel/ms format https://github.com/vercel/ms</td></tr><tr><td>PAGESPEED_API_KEY</td><td>Optional</td><td><code>string</code></td><td>API Key for PageSpeed requests</td><td></td></tr><tr><td>SYSTEM_EMAIL_HOST</td><td>Required</td><td><code>string</code></td><td>Host to send System Emails From</td><td></td></tr><tr><td>SYSTEM_EMAIL_PORT</td><td>Required</td><td><code>number</code></td><td>Port for System Email Host</td><td></td></tr><tr><td>SYSTEM_EMAIL_ADDRESS</td><td>Required</td><td><code>string</code></td><td>System Email Address</td><td></td></tr><tr><td>SYSTEM_EMAIL_PASSWORD</td><td>Required</td><td><code>string</code></td><td>System Email Password</td><td></td></tr></tbody></table>
<table><thead><tr><th width="239">ENV Variable Name</th><th width="149">Required/Optional</th><th width="116">Type</th><th>Description</th><th>Accepted Values</th></tr></thead><tbody><tr><td>CLIENT_HOST</td><td>Required</td><td><code>string</code></td><td>Frontend Host</td><td></td></tr><tr><td>JWT_SECRET</td><td>Required</td><td><code>string</code></td><td>JWT secret</td><td></td></tr><tr><td>REFRESH_TOKEN_SECRET</td><td>Required</td><td><code>string</code></td><td>Refresh JWT secret</td><td></td></tr><tr><td>DB_TYPE</td><td>Optional</td><td><code>string</code></td><td>Specify DB to use</td><td><code>MongoDB | FakeDB</code></td></tr><tr><td>DB_CONNECTION_STRING</td><td>Required</td><td><code>string</code></td><td>Specifies URL for MongoDB Database</td><td></td></tr><tr><td>PORT</td><td>Optional</td><td><code>integer</code></td><td>Specifies Port for Server</td><td></td></tr><tr><td>LOGIN_PAGE_URL</td><td>Required</td><td><code>string</code></td><td>Login url to be used in emailing service</td><td></td></tr><tr><td>REDIS_HOST</td><td>Required</td><td><code>string</code></td><td>Host address for Redis database</td><td></td></tr><tr><td>REDIS_PORT</td><td>Required</td><td><code>integer</code></td><td>Port for Redis database</td><td></td></tr><tr><td>TOKEN_TTL</td><td>Optional</td><td><code>string</code></td><td>Time for token to live</td><td>In vercel/ms format https://github.com/vercel/ms</td></tr><tr><td>REFRESH_TOKEN_TTL</td><td>Optional</td><td><code>string</code></td><td>Time for refresh token to live</td><td></td></tr><tr><td>PAGESPEED_API_KEY</td><td>Optional</td><td><code>string</code></td><td>API Key for PageSpeed requests</td><td></td></tr><tr><td>SYSTEM_EMAIL_HOST</td><td>Required</td><td><code>string</code></td><td>Host to send System Emails From</td><td></td></tr><tr><td>SYSTEM_EMAIL_PORT</td><td>Required</td><td><code>number</code></td><td>Port for System Email Host</td><td></td></tr><tr><td>SYSTEM_EMAIL_ADDRESS</td><td>Required</td><td><code>string</code></td><td>System Email Address</td><td></td></tr><tr><td>SYSTEM_EMAIL_PASSWORD</td><td>Required</td><td><code>string</code></td><td>System Email Password</td><td></td></tr></tbody></table>
---