Merge branch 'bluewave-labs:master' into monitors
This commit is contained in:
27 files changed
+548
-74
No files matched your search
+11
-5
@@ -17,6 +17,7 @@ All types of contributions are encouraged and valued. See the [Table of Contents
|
||||
- [I Want To Contribute](#i-want-to-contribute)
|
||||
- [Reporting Bugs](#reporting-bugs)
|
||||
- [Suggesting Enhancements](#suggesting-enhancements)
|
||||
- [Design Guidelines][#design-guidelines]
|
||||
- [Styleguides](#Developer-guide-for-issue-lifecycle-process)
|
||||
|
||||
|
||||
@@ -54,7 +55,7 @@ A good bug report shouldn't leave others needing to chase you up for more inform
|
||||
- Collect information about the bug:
|
||||
- Stack trace (Traceback)
|
||||
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
|
||||
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
|
||||
- Version of the interpreter, compiler, SDK, runtime environment and package manager, depending on what seems relevant.
|
||||
- Possibly your input and the output
|
||||
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
|
||||
|
||||
@@ -80,15 +81,11 @@ Once it's filed:
|
||||
|
||||
This section guides you through submitting an enhancement suggestion for CONTRIBUTING.md, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
|
||||
|
||||
|
||||
#### Before Submitting an Enhancement
|
||||
|
||||
- Make sure that you are using the latest version.
|
||||
- Find out if the functionality is already covered, maybe by an individual configuration.
|
||||
- Perform a [search](/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
||||
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
|
||||
|
||||
|
||||
#### How Do I Submit a Good Enhancement Suggestion?
|
||||
|
||||
Enhancement suggestions are tracked as [GitHub issues](/issues).
|
||||
@@ -99,6 +96,15 @@ Enhancement suggestions are tracked as [GitHub issues](/issues).
|
||||
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to.
|
||||
- **Explain why this enhancement would be useful** to most CONTRIBUTING.md users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
|
||||
|
||||
### Design guidelines
|
||||
|
||||
We have a Figma file that includes:
|
||||
|
||||
- All the dashboard elements and components
|
||||
- The design guideline for the app
|
||||
|
||||
You can see it [here](https://www.figma.com/design/RPSfaw66HjzSwzntKcgDUV/Uptime-Genie?node-id=0-1&t=WqOFv9jqNTFGItpL-1). Since it is read-only, we encourage you to copy to your own Figma page, then work on it.
|
||||
|
||||
### Developer Guide for Issue Lifecycle Process
|
||||
|
||||
[This document](https://docs.google.com/document/d/1Gy3LiimGUNoSiWAMbwyK3SeMADcCMjCLu6cQYoawtSE/edit#heading=h.1lj2lgut6m7h) outlines the process every developer should follow for managing the issues lifecycle.
|
||||
|
||||
Generated
+55
-1
@@ -12,6 +12,7 @@
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@fontsource/roboto": "^5.0.13",
|
||||
"@mui/icons-material": "^5.15.17",
|
||||
"@mui/lab": "^5.0.0-alpha.170",
|
||||
"@mui/material": "^5.15.16",
|
||||
"@mui/x-charts": "^7.5.1",
|
||||
"@mui/x-data-grid": "7.3.2",
|
||||
@@ -26,7 +27,8 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "9.1.2",
|
||||
"react-router": "^6.23.0",
|
||||
"react-router-dom": "^6.23.1"
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-toastify": "^10.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.66",
|
||||
@@ -1158,6 +1160,46 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/lab": {
|
||||
"version": "5.0.0-alpha.170",
|
||||
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-5.0.0-alpha.170.tgz",
|
||||
"integrity": "sha512-0bDVECGmrNjd3+bLdcLiwYZ0O4HP5j5WSQm5DV6iA/Z9kr8O6AnvZ1bv9ImQbbX7Gj3pX4o43EKwCutj3EQxQg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.9",
|
||||
"@mui/base": "5.0.0-beta.40",
|
||||
"@mui/system": "^5.15.15",
|
||||
"@mui/types": "^7.2.14",
|
||||
"@mui/utils": "^5.15.14",
|
||||
"clsx": "^2.1.0",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@mui/material": ">=5.15.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "5.15.19",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-5.15.19.tgz",
|
||||
@@ -4688,6 +4730,18 @@
|
||||
"react-dom": ">=16.8"
|
||||
}
|
||||
},
|
||||
"node_modules/react-toastify": {
|
||||
"version": "10.0.5",
|
||||
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz",
|
||||
"integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-transition-group": {
|
||||
"version": "4.4.5",
|
||||
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
|
||||
|
||||
+3
-1
@@ -14,6 +14,7 @@
|
||||
"@emotion/styled": "^11.11.5",
|
||||
"@fontsource/roboto": "^5.0.13",
|
||||
"@mui/icons-material": "^5.15.17",
|
||||
"@mui/lab": "^5.0.0-alpha.170",
|
||||
"@mui/material": "^5.15.16",
|
||||
"@mui/x-charts": "^7.5.1",
|
||||
"@mui/x-data-grid": "7.3.2",
|
||||
@@ -28,7 +29,8 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "9.1.2",
|
||||
"react-router": "^6.23.0",
|
||||
"react-router-dom": "^6.23.1"
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-toastify": "^10.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.2.66",
|
||||
|
||||
@@ -15,6 +15,7 @@ import ForgotPassword from "./Pages/ForgotPassword";
|
||||
import CheckEmail from "./Pages/CheckEmail";
|
||||
import SetNewPassword from "./Pages/SetNewPassword";
|
||||
import NewPasswordConfirmed from "./Pages/NewPasswordConfirmed";
|
||||
import ToastComponent from "./Components/Toast";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
@@ -30,6 +31,7 @@ function App() {
|
||||
<Route exact path="/register" element={<Register />} />
|
||||
<Route exact path="/login" element={<Login />} />
|
||||
<Route exact path="/demo" element={<Demo />} />
|
||||
<Route path="/toast" element={<ToastComponent />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
<Route path="/playground" element={<PlayGround />} />
|
||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||
|
||||
+9
-3
@@ -1,5 +1,4 @@
|
||||
import "./announcementsDualButtonWithIcon.css";
|
||||
import React from "react";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@mui/material";
|
||||
@@ -12,6 +11,7 @@ import { useTheme } from "@mui/material";
|
||||
* @param {string} props.body - The announcement body text content
|
||||
* @param {string} props.esc - The text for the escape button (usually "Close")
|
||||
* @param {string} props.primary - The text for the primary button
|
||||
* @param {function} props.closeToast - Function to close the toast notification
|
||||
* @returns {JSX.Element} - Renders the announcement dual button with icon component
|
||||
*/
|
||||
|
||||
@@ -21,6 +21,7 @@ const AnnouncementsDualButtonWithIcon = ({
|
||||
body,
|
||||
esc,
|
||||
primary,
|
||||
closeToast,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@@ -40,14 +41,18 @@ const AnnouncementsDualButtonWithIcon = ({
|
||||
{body && <div className="announcement-content-body">{body}</div>}
|
||||
{(esc || primary) && (
|
||||
<div className="announcement-content-controllers">
|
||||
{esc && <div className="controllers-button-esc">{esc}</div>}
|
||||
{esc && (
|
||||
<div className="controllers-button-esc" onClick={closeToast}>
|
||||
{esc}
|
||||
</div>
|
||||
)}
|
||||
{primary && (
|
||||
<div className="controllers-button-primary">{primary}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="announcement-close">
|
||||
<div className="announcement-close" onClick={closeToast}>
|
||||
<CloseIcon style={{ fill: "#98A2B3" }} />
|
||||
</div>
|
||||
</div>
|
||||
@@ -60,6 +65,7 @@ AnnouncementsDualButtonWithIcon.propTypes = {
|
||||
body: PropTypes.string,
|
||||
esc: PropTypes.string,
|
||||
primary: PropTypes.string,
|
||||
closeToast: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default AnnouncementsDualButtonWithIcon;
|
||||
@@ -0,0 +1,105 @@
|
||||
import LoadingButton from "@mui/lab/LoadingButton";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const levelConfig = {
|
||||
primary: {
|
||||
variant: "contained",
|
||||
color: "primary",
|
||||
},
|
||||
secondary: {
|
||||
variant: "outlined",
|
||||
color: "secondary",
|
||||
},
|
||||
tertiary: {
|
||||
variant: "text",
|
||||
color: "tertiary",
|
||||
},
|
||||
error: {
|
||||
variant: "contained",
|
||||
color: "error",
|
||||
},
|
||||
imagePrimary: {
|
||||
color: "primary",
|
||||
variant: "contained",
|
||||
},
|
||||
imageSecondary: {
|
||||
color: "secondary",
|
||||
variant: "outlined",
|
||||
},
|
||||
imageTertiary: {
|
||||
color: "tertiary",
|
||||
variant: "text",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* ButtonSpinner component displays a button with loading loadingText capability.
|
||||
* @component
|
||||
* @param {Object} props
|
||||
* @param {'primary' | 'secondary' | 'tertiary' | 'error' | 'imagePrimary' | 'imageSecondary' | 'imageTertiary'} props.level - The style level of the button.
|
||||
* @param {string} props.label - The label text displayed on the button.
|
||||
* @param {React.ReactNode} [props.img] - Icon or image element to display within the button.
|
||||
* @param {boolean} [props.disabled=false] - Determines if the button is disabled.
|
||||
* @param {string} [props.loadingText] - Text displayed when the button is in a loading state.
|
||||
* @param {'start' | 'end'} [props.position] - Specifies where the icon or loading loadingText should be positioned relative to the label.
|
||||
* @param {function} props.onClick - Callback function invoked when the button is clicked.
|
||||
* @param {boolean} props.isLoading - Indicates if the button is in a loading state.
|
||||
* @param {Object} [props.sx] - Additional styles to apply to the button.
|
||||
* @returns {JSX.Element}
|
||||
* @example
|
||||
* // Render a primary button with an icon at the end
|
||||
* <ButtonSpinner
|
||||
* level="primary"
|
||||
* label="Save"
|
||||
* img={<SaveIcon />}
|
||||
* position="end"
|
||||
* onClick={handleSaveClick}
|
||||
* isLoading={loading}
|
||||
* />
|
||||
*/
|
||||
|
||||
const ButtonSpinner = ({
|
||||
level,
|
||||
label,
|
||||
disabled,
|
||||
loadingText,
|
||||
position,
|
||||
img,
|
||||
onClick,
|
||||
isLoading,
|
||||
sx,
|
||||
}) => {
|
||||
const { variant, color } = levelConfig[level];
|
||||
//if both a loadingPosition and loadingIndicator are provided, the spinner overlaps with the text and it breaks
|
||||
if (position && loadingText) loadingText = null;
|
||||
return (
|
||||
<LoadingButton
|
||||
variant={variant}
|
||||
color={color}
|
||||
disabled={disabled}
|
||||
loadingIndicator={loadingText}
|
||||
loadingPosition={position}
|
||||
startIcon={position === "start" && img}
|
||||
endIcon={position === "end" && img}
|
||||
onClick={onClick}
|
||||
loading={isLoading}
|
||||
sx={{ textTransform: "none", ...sx }}
|
||||
>
|
||||
<span>{label}</span>
|
||||
</LoadingButton>
|
||||
);
|
||||
};
|
||||
|
||||
ButtonSpinner.propTypes = {
|
||||
level: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
loadingText: PropTypes.string,
|
||||
position: PropTypes.oneOf(["start", "end"]),
|
||||
img: PropTypes.node,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
sx: PropTypes.object,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default ButtonSpinner;
|
||||
@@ -0,0 +1,3 @@
|
||||
.Toastify__toast-container {
|
||||
width: auto;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import ComplexAlert from "../../Components/Icons/ComplexAlert/ComplexAlert";
|
||||
import AnnouncementsDualButtonWithIcon from "../../Components/Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
|
||||
import "react-toastify/dist/ReactToastify.css";
|
||||
import { toast, ToastContainer } from "react-toastify";
|
||||
import "./index.css";
|
||||
|
||||
const ToastComponent = () => {
|
||||
const displayMsg = () => {
|
||||
toast(
|
||||
({ closeToast, toastProps }) => (
|
||||
<AnnouncementsDualButtonWithIcon
|
||||
icon={<ComplexAlert theme="red" />}
|
||||
subject="There was a problem with that action"
|
||||
body="Lorem ipsum dolor sit amet consectetur adipisicing elit. Aliquid pariatur, ipsum dolor."
|
||||
esc="Dismiss"
|
||||
primary="Learn more"
|
||||
closeToast={closeToast}
|
||||
/>
|
||||
),
|
||||
{ closeButton: false }
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button onClick={displayMsg}>Click me</button>
|
||||
<ToastContainer className="Toastify__toast-container" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ToastComponent;
|
||||
@@ -1,5 +1,6 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import Button from "../../Components/Button/";
|
||||
import ButtonSpinner from "../../Components/ButtonSpinner";
|
||||
import Link from "../../Components/Link";
|
||||
import {
|
||||
useTheme,
|
||||
@@ -13,6 +14,7 @@ import {
|
||||
Tab,
|
||||
Tabs,
|
||||
Typography,
|
||||
Stack,
|
||||
} from "@mui/material";
|
||||
import { ColoredLabel, StatusLabel } from "../../Components/Label/";
|
||||
import Avatar from "../../Components/Avatar/";
|
||||
@@ -25,6 +27,8 @@ import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider";
|
||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import UploadIcon from '@mui/icons-material/Upload';
|
||||
import SendIcon from '@mui/icons-material/Send';
|
||||
|
||||
// Redux
|
||||
import { useSelector } from "react-redux";
|
||||
@@ -181,6 +185,16 @@ const Demo = () => {
|
||||
// *********************************
|
||||
const monitorState = useSelector((state) => state.monitors);
|
||||
const { monitors, msg } = monitorState;
|
||||
|
||||
//Button Spinner state
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const handleButtonSpinnerClick = () => {
|
||||
setIsLoading(true);
|
||||
|
||||
setTimeout(() => {
|
||||
setIsLoading(false);
|
||||
}, 4000);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<div style={{ padding: "4rem", border: "1px solid black" }}>
|
||||
@@ -352,6 +366,54 @@ const Demo = () => {
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="section">Section</h4>
|
||||
<Section monitors={demoMonitors} />
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
<h4 id="section">Loading Buttons</h4>
|
||||
<Stack direction="row" justifyContent="center" spacing={3}>
|
||||
<ButtonSpinner
|
||||
level="primary"
|
||||
label="Submit"
|
||||
onClick={handleButtonSpinnerClick}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<ButtonSpinner
|
||||
level="secondary"
|
||||
label="Fetch Data"
|
||||
loadingText="Fetching..."
|
||||
onClick={handleButtonSpinnerClick}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<ButtonSpinner
|
||||
level="tertiary"
|
||||
label="Add"
|
||||
loadingText="Adding..."
|
||||
onClick={handleButtonSpinnerClick}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<ButtonSpinner
|
||||
level="imagePrimary"
|
||||
label="Upload"
|
||||
position="start"
|
||||
img={<UploadIcon/>}
|
||||
onClick={handleButtonSpinnerClick}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<ButtonSpinner
|
||||
level="imageSecondary"
|
||||
label="Send"
|
||||
position="end"
|
||||
img={<SendIcon/>}
|
||||
onClick={handleButtonSpinnerClick}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<ButtonSpinner
|
||||
level="error"
|
||||
label="Disabled"
|
||||
onClick={handleButtonSpinnerClick}
|
||||
isLoading={isLoading}
|
||||
disabled={true}
|
||||
/>
|
||||
</Stack>
|
||||
<Divider sx={{ margin: `${theme.spacing(2)}` }} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,6 +13,7 @@ import AnnouncementUpdateSubscription from "../../Components/Announcements/Annou
|
||||
import PlayGroundCharts from "./PlayGround-Charts";
|
||||
import PlayGroundPopupModals from "./PlayGround-Popup-Modals";
|
||||
import PlayGroundTooltips from "./PlayGround-Tooltips";
|
||||
import Toast from "../../Components/Toast";
|
||||
|
||||
// This Component is just for the development and test
|
||||
// purposes and just to see what my components look like while development
|
||||
@@ -52,6 +53,7 @@ function PlayGround() {
|
||||
hasCopyButton={true}
|
||||
hintText="This is a hint text to help user."
|
||||
/>
|
||||
<Toast />
|
||||
|
||||
<hr />
|
||||
{/* Now, illustration of the Description text fields */}
|
||||
|
||||
@@ -10,7 +10,18 @@
|
||||
|
||||
# BlueWave Uptime
|
||||
|
||||
BlueWave uptime monitoring application
|
||||
BlueWave Uptime is an open source server monitoring application. It is a tool used to track the operational status and performance of servers and websites. It regularly checks whether a server/website is accessible and performing optimally, providing real-time alerts and reports on the availability, downtime, and response time of the monitored services.
|
||||
|
||||
## Contributing
|
||||
|
||||
You are welcome to provide contributions to the project. It uses React on the FE, and Nodejs and MongoDB on the BE, hence if you are comfortable with working with those technologies, you are encouraged to send your PRs. Please read [Contributor's guideline](https://github.com/bluewave-labs/bluewave-uptime/blob/master/CONTRIBUTING.md)
|
||||
|
||||
Note that We have a Figma file that includes:
|
||||
|
||||
- All the dashboard elements and components
|
||||
- The design guideline for the app
|
||||
|
||||
You can see the designs [here](https://www.figma.com/design/RPSfaw66HjzSwzntKcgDUV/Uptime-Genie?node-id=0-1&t=WqOFv9jqNTFGItpL-1). Since it is read-only, we encourage you to copy to your own Figma page, then work on it.
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ const {
|
||||
editAlertBodyValidation,
|
||||
deleteAlertParamValidation,
|
||||
} = require("../validation/joi");
|
||||
|
||||
const { successMessages } = require("../utils/messages");
|
||||
const SERVICE_NAME = "alerts";
|
||||
|
||||
/**
|
||||
@@ -38,7 +38,7 @@ const createAlert = async (req, res, next) => {
|
||||
const alert = await req.db.createAlert(alertData);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: "Alert created", data: alert });
|
||||
.json({ success: true, msg: successMessages.ALERT_CREATE, data: alert });
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -66,9 +66,11 @@ const getAlertsByUserId = async (req, res, next) => {
|
||||
|
||||
try {
|
||||
const alerts = await req.db.getAlertsByUserId(req.params.userId);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: "Got alerts", data: alerts });
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.ALERT_GET_BY_USER,
|
||||
data: alerts,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -90,7 +92,7 @@ const getAlertsByMonitorId = async (req, res, next) => {
|
||||
const alerts = await req.db.getAlertsByMonitorId(req.params.monitorId);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Got alerts by Monitor",
|
||||
msg: successMessages.ALERT_GET_BY_MONITOR,
|
||||
data: alerts,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -114,7 +116,7 @@ const getAlertById = async (req, res, next) => {
|
||||
const alert = await req.db.getAlertById(req.params.alertId);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Got Alert By alertID",
|
||||
msg: successMessages.ALERT_GET_BY_ID,
|
||||
data: alert,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -141,7 +143,7 @@ const editAlert = async (req, res, next) => {
|
||||
const alert = await req.db.editAlert(req.params.alertId, alertData);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Edited alert",
|
||||
msg: successMessages.ALERT_EDIT,
|
||||
data: alert,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -164,7 +166,7 @@ const deleteAlert = async (req, res, next) => {
|
||||
const deleted = await req.db.deleteAlert(req.params.alertId);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Deleted alert",
|
||||
msg: successMessages.ALERT_DELETE,
|
||||
data: deleted,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@@ -10,6 +10,7 @@ const {
|
||||
} = require("../validation/joi");
|
||||
const logger = require("../utils/logger");
|
||||
require("dotenv").config();
|
||||
const { errorMessages, successMessages } = require("../utils/messages");
|
||||
var jwt = require("jsonwebtoken");
|
||||
const SERVICE_NAME = "auth";
|
||||
const { sendEmail } = require("../utils/sendEmail");
|
||||
@@ -49,7 +50,7 @@ const registerController = async (req, res, next) => {
|
||||
// Create a new user
|
||||
try {
|
||||
const newUser = await req.db.insertUser(req, res);
|
||||
logger.info("New user created!", {
|
||||
logger.info(successMessages.AUTH_CREATE_USER, {
|
||||
service: SERVICE_NAME,
|
||||
userId: newUser._id,
|
||||
});
|
||||
@@ -64,9 +65,11 @@ const registerController = async (req, res, next) => {
|
||||
"Registered."
|
||||
);
|
||||
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: "User created", data: token });
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_CREATE_USER,
|
||||
data: token,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -92,7 +95,7 @@ const loginController = async (req, res, next) => {
|
||||
|
||||
const match = await user.comparePassword(req.body.password);
|
||||
if (match !== true) {
|
||||
throw new Error("Incorrect password");
|
||||
throw new Error(errorMessages.AUTH_INCORRECT_PASSWORD);
|
||||
}
|
||||
|
||||
// Remove password from user object. Should this be abstracted to DB layer?
|
||||
@@ -101,9 +104,11 @@ const loginController = async (req, res, next) => {
|
||||
|
||||
// Happy path, return token
|
||||
const token = issueToken(userWithoutPassword);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: "Found user", data: token });
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_LOGIN_USER,
|
||||
data: token,
|
||||
});
|
||||
} catch (error) {
|
||||
error.status = 500;
|
||||
// Anything else should be an error
|
||||
@@ -124,7 +129,7 @@ const userEditController = async (req, res, next) => {
|
||||
}
|
||||
|
||||
if (req.params.userId !== req.user._id.toString()) {
|
||||
const error = new Error("Unauthorized access");
|
||||
const error = new Error(errorMessages.AUTH_UNAUTHORIZED);
|
||||
error.status = 401;
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -133,9 +138,11 @@ const userEditController = async (req, res, next) => {
|
||||
|
||||
try {
|
||||
const updatedUser = await req.db.updateUser(req, res);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: "User updated", data: updatedUser });
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_UPDATE_USER,
|
||||
data: updatedUser,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -166,7 +173,7 @@ const recoveryRequestController = async (req, res, next) => {
|
||||
);
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Created recovery token",
|
||||
msg: successMessages.AUTH_CREATE_RECOVERY_TOKEN,
|
||||
});
|
||||
}
|
||||
// TODO Email token to user
|
||||
@@ -193,7 +200,7 @@ const validateRecoveryTokenController = async (req, res, next) => {
|
||||
// TODO Redirect user to reset password after validating token
|
||||
return res.status(200).json({
|
||||
success: true,
|
||||
msg: "Token is valid",
|
||||
msg: successMessages.AUTH_VERIFY_RECOVERY_TOKEN,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
@@ -216,7 +223,13 @@ const resetPasswordController = async (req, res, next) => {
|
||||
try {
|
||||
await newPasswordValidation.validateAsync(req.body);
|
||||
user = await req.db.resetPassword(req, res);
|
||||
res.status(200).json({ success: true, msg: "Password reset", data: user });
|
||||
res
|
||||
.status(200)
|
||||
.json({
|
||||
success: true,
|
||||
msg: successMessages.AUTH_RESET_PASSWORD,
|
||||
data: user,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
|
||||
@@ -4,6 +4,7 @@ const {
|
||||
getChecksParamValidation,
|
||||
deleteChecksParamValidation,
|
||||
} = require("../validation/joi");
|
||||
const { successMessages } = require("../utils/messages");
|
||||
const SERVICE_NAME = "check";
|
||||
|
||||
const createCheck = async (req, res, next) => {
|
||||
@@ -23,7 +24,7 @@ const createCheck = async (req, res, next) => {
|
||||
const check = await req.db.createCheck(checkData);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: "Check created", data: check });
|
||||
.json({ success: true, msg: successMessages.CHECK_CREATE, data: check });
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -45,7 +46,7 @@ const getChecks = async (req, res, next) => {
|
||||
const checks = await req.db.getChecks(req.params.monitorId);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: "Checks retrieved", data: checks });
|
||||
.json({ success: true, msg: successMessages.CHECK_GET, data: checks });
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -67,7 +68,11 @@ const deleteChecks = async (req, res, next) => {
|
||||
const deletedCount = await req.db.deleteChecks(req.params.monitorId);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: "Checks deleted", data: { deletedCount } });
|
||||
.json({
|
||||
success: true,
|
||||
msg: successMessages.CHECK_DELETE,
|
||||
data: { deletedCount },
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
|
||||
@@ -5,6 +5,8 @@ const {
|
||||
} = require("../validation/joi");
|
||||
|
||||
const SERVICE_NAME = "monitorController";
|
||||
const { errorMessages, successMessages } = require("../utils/messages");
|
||||
const { error } = require("winston");
|
||||
|
||||
/**
|
||||
* Returns all monitors
|
||||
@@ -17,7 +19,11 @@ const SERVICE_NAME = "monitorController";
|
||||
const getAllMonitors = async (req, res, next) => {
|
||||
try {
|
||||
const monitors = await req.db.getAllMonitors();
|
||||
return res.json({ success: true, msg: "Monitors found", data: monitors });
|
||||
return res.json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_GET_ALL,
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -45,12 +51,16 @@ const getMonitorById = async (req, res, next) => {
|
||||
try {
|
||||
const monitor = await req.db.getMonitorById(req, res);
|
||||
if (!monitor) {
|
||||
const error = new Error("Monitor not found");
|
||||
const error = new Error(errorMessages.MONITOR_GET_BY_ID);
|
||||
error.status = 404;
|
||||
throw error;
|
||||
}
|
||||
|
||||
return res.json({ success: true, msg: "Monitor found", data: monitor });
|
||||
return res.json({
|
||||
success: true,
|
||||
msg: successMessages.MONTIOR_GET_BY_ID,
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -78,14 +88,14 @@ const getMonitorsByUserId = async (req, res, next) => {
|
||||
const monitors = await req.db.getMonitorsByUserId(req, res);
|
||||
|
||||
if (monitors && monitors.length === 0) {
|
||||
const err = new Error("No monitors found");
|
||||
const err = new Error(errorMessages.MONITOR_GET_BY_USER_ID);
|
||||
err.status = 404;
|
||||
throw err;
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
msg: `Monitors for user ${userId} found`,
|
||||
msg: successMessages.MONITOR_GET_BY_USER_ID(userId),
|
||||
data: monitors,
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -118,9 +128,11 @@ const createMonitor = async (req, res, next) => {
|
||||
const monitor = await req.db.createMonitor(req, res);
|
||||
// Add monitor to job queue
|
||||
req.jobQueue.addJob(monitor._id, monitor);
|
||||
return res
|
||||
.status(201)
|
||||
.json({ success: true, msg: "Monitor created", data: monitor });
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_CREATE,
|
||||
data: monitor,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -157,7 +169,9 @@ const deleteMonitor = async (req, res, next) => {
|
||||
* when it is deleted so there is no orphaned data
|
||||
* We also need to make sure to stop all running services for this monitor
|
||||
*/
|
||||
return res.status(200).json({ success: true, msg: "Monitor deleted" });
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: successMessages.MONITOR_DELETE });
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -188,7 +202,11 @@ const editMonitor = async (req, res, next) => {
|
||||
const editedMonitor = await req.db.editMonitor(req, res);
|
||||
return res
|
||||
.status(200)
|
||||
.json({ success: true, msg: "Monitor edited", data: editedMonitor });
|
||||
.json({
|
||||
success: true,
|
||||
msg: successMessages.MONITOR_EDIT,
|
||||
data: editedMonitor,
|
||||
});
|
||||
} catch (error) {
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
|
||||
+12
-9
@@ -6,7 +6,8 @@ const Alert = require("../models/Alert");
|
||||
const RecoveryToken = require("../models/RecoveryToken");
|
||||
const crypto = require("crypto");
|
||||
const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key
|
||||
|
||||
const { errorMessages, successMessages } = require("../utils/messages");
|
||||
const { error } = require("console");
|
||||
const connect = async () => {
|
||||
try {
|
||||
await mongoose.connect(process.env.DB_CONNECTION_STRING);
|
||||
@@ -36,7 +37,7 @@ const insertUser = async (req, res) => {
|
||||
return await UserModel.findOne({ _id: newUser._id }).select("-password"); // .select() doesn't work with create, need to save then find
|
||||
} catch (error) {
|
||||
if (error.code === DUPLICATE_KEY_CODE) {
|
||||
throw new Error("Email already exists");
|
||||
throw new Error(errorMessages.DB_USER_EXISTS);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
@@ -61,7 +62,7 @@ const getUserByEmail = async (req, res) => {
|
||||
if (user) {
|
||||
return user;
|
||||
} else {
|
||||
throw new Error("User not found");
|
||||
throw new Error(errorMessages.DB_USER_NOT_FOUND);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
@@ -125,7 +126,7 @@ const validateRecoveryToken = async (req, res) => {
|
||||
if (recoveryToken !== null) {
|
||||
return recoveryToken;
|
||||
} else {
|
||||
throw new Error("Token not found");
|
||||
throw new Error(errorMessages.DB_TOKEN_NOT_FOUND);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
@@ -142,7 +143,7 @@ const resetPassword = async (req, res) => {
|
||||
|
||||
const match = await user.comparePassword(newPassword);
|
||||
if (match === true) {
|
||||
throw new Error("New password must be different from old password");
|
||||
throw new Error(errorMessages.DB_RESET_PASSWORD_BAD_MATCH);
|
||||
}
|
||||
|
||||
if (user !== null) {
|
||||
@@ -155,7 +156,7 @@ const resetPassword = async (req, res) => {
|
||||
}).select("-password");
|
||||
return userWithoutPassword;
|
||||
} else {
|
||||
throw new Error("User not found");
|
||||
throw new Error(errorMessages.DB_USER_NOT_FOUND);
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
@@ -249,7 +250,7 @@ const deleteMonitor = async (req, res) => {
|
||||
try {
|
||||
const monitor = await Monitor.findByIdAndDelete(monitorId);
|
||||
if (!monitor) {
|
||||
throw new Error(`Monitor with id ${monitorId} not found`);
|
||||
throw new Error(errorMessages.DB_FIND_MONTIOR_BY_ID(monitorId));
|
||||
}
|
||||
return monitor;
|
||||
} catch (error) {
|
||||
@@ -338,7 +339,7 @@ const deleteChecks = async (monitorId) => {
|
||||
if (result.deletedCount > 0) {
|
||||
return result.deletedCount;
|
||||
} else {
|
||||
throw new Error(`No checks found for monitor with id ${monitorId}`);
|
||||
throw new Error(errorMessages.DB_DELETE_CHECKS(monitorId));
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
@@ -458,7 +459,9 @@ const deleteAlert = async (alertId) => {
|
||||
const result = await Alert.findByIdAndDelete(alertId);
|
||||
if (result) {
|
||||
return result;
|
||||
} else throw new Error(`Alert with id ${alertId} not found`);
|
||||
} else {
|
||||
throw new Error(errorMessages.DB_DELETE_ALERT(alertId));
|
||||
}
|
||||
} catch (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
Executable → Regular
File mode changed.
@@ -93,6 +93,16 @@ const startApp = async () => {
|
||||
|
||||
await connectDbAndRunServer(app, db);
|
||||
const jobQueue = await JobQueue.createJobQueue(db);
|
||||
|
||||
const cleanup = async () => {
|
||||
console.log("Shutting down gracefully");
|
||||
await jobQueue.obliterate();
|
||||
console.log("Finished cleanup");
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on("SIGINT", cleanup);
|
||||
process.on("SIGTERM", cleanup);
|
||||
};
|
||||
|
||||
startApp().catch((error) => {
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
const logger = require("../utils/logger");
|
||||
const { errorMessages } = require("../utils/messages");
|
||||
|
||||
const handleErrors = (error, req, res, next) => {
|
||||
const status = error.status || 500;
|
||||
const message = error.message || "Something went wrong";
|
||||
const service = error.service || "Unknown service";
|
||||
const message = error.message || errorMessages.FRIENDLY_ERROR;
|
||||
const service = error.service || errorMessages.UNKNOWN_SERVICE;
|
||||
logger.error(error.message, { service: service });
|
||||
res.status(status).json({ success: false, msg: message });
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@ const jwt = require("jsonwebtoken");
|
||||
const logger = require("../utils/logger");
|
||||
const SERVICE_NAME = "verifyJWT";
|
||||
const TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
const { errorMessages } = require("../utils/messages");
|
||||
/**
|
||||
* Verifies the JWT token
|
||||
* @function
|
||||
@@ -15,7 +15,7 @@ const verifyJWT = (req, res, next) => {
|
||||
const token = req.headers["authorization"];
|
||||
// Make sure a token is provided
|
||||
if (!token) {
|
||||
const error = new Error("No token provided");
|
||||
const error = new Error(error.errorMessages.NO_AUTH_TOKEN);
|
||||
error.status = 401;
|
||||
error.service = SERVICE_NAME;
|
||||
next(error);
|
||||
@@ -27,8 +27,12 @@ const verifyJWT = (req, res, next) => {
|
||||
// Verify the token's authenticity
|
||||
jwt.verify(parsedToken, process.env.JWT_SECRET, (err, decoded) => {
|
||||
if (err) {
|
||||
logger.error("Invalid token", { service: SERVICE_NAME });
|
||||
return res.status(401).json({ success: false, msg: "Invalid token" });
|
||||
logger.error(errorMessages.INVALID_AUTH_TOKEN, {
|
||||
service: SERVICE_NAME,
|
||||
});
|
||||
return res
|
||||
.status(401)
|
||||
.json({ success: false, msg: errorMessages.INVALID_AUTH_TOKEN });
|
||||
}
|
||||
//Add the user to the request object for use in the route
|
||||
req.user = decoded;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const logger = require("../utils/logger");
|
||||
const SERVICE_NAME = "verifyOwnership";
|
||||
const { errorMessages } = require("../utils/messages");
|
||||
|
||||
const verifyOwnership = (Model, paramName) => {
|
||||
return async (req, res, next) => {
|
||||
@@ -9,10 +10,10 @@ const verifyOwnership = (Model, paramName) => {
|
||||
const doc = await Model.findById(documentId);
|
||||
//If the document is not found, return a 404 error
|
||||
if (!doc) {
|
||||
logger.error("Document not found", {
|
||||
logger.error(errorMessages.VERIFY_OWNER_NOT_FOUND, {
|
||||
service: SERVICE_NAME,
|
||||
});
|
||||
const error = new Error("Document not found");
|
||||
const error = new Error(errorMessages.VERIFY_OWNER_NOT_FOUND);
|
||||
error.status = 404;
|
||||
throw error;
|
||||
}
|
||||
@@ -20,7 +21,7 @@ const verifyOwnership = (Model, paramName) => {
|
||||
// If the userID does not match the document's userID, return a 403 error
|
||||
if (userId.toString() !== doc.userId.toString()) {
|
||||
console.log("boom");
|
||||
const error = new Error("Unauthorized access");
|
||||
const error = new Error(errorMessages.VERIFY_OWNER_UNAUTHORIZED);
|
||||
error.status = 403;
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ const MonitorSchema = mongoose.Schema(
|
||||
description: {
|
||||
type: String,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ["http", "ping"],
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
||||
Generated
+10
@@ -21,6 +21,7 @@
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"mailersend": "^2.2.0",
|
||||
"mongoose": "^8.3.3",
|
||||
"ping": "0.4.4",
|
||||
"winston": "^3.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -2205,6 +2206,15 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/ping": {
|
||||
"version": "0.4.4",
|
||||
"resolved": "https://registry.npmjs.org/ping/-/ping-0.4.4.tgz",
|
||||
"integrity": "sha512-56ZMC0j7SCsMMLdOoUg12VZCfj/+ZO+yfOSjaNCRrmZZr6GLbN2X/Ui56T15dI8NhiHckaw5X2pvyfAomanwqQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-addr": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"mailersend": "^2.2.0",
|
||||
"mongoose": "^8.3.3",
|
||||
"ping": "0.4.4",
|
||||
"winston": "^3.13.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -6,8 +6,10 @@ const connection = {
|
||||
};
|
||||
const JOBS_PER_WORKER = 5;
|
||||
const logger = require("../utils/logger");
|
||||
const { errorMessages, successMessages } = require("../utils/messages");
|
||||
const SERVICE_NAME = "JobQueue";
|
||||
const axios = require("axios");
|
||||
const ping = require("ping");
|
||||
|
||||
class JobQueue {
|
||||
/**
|
||||
@@ -35,6 +37,9 @@ class JobQueue {
|
||||
try {
|
||||
queue.db = db;
|
||||
const monitors = await db.getAllMonitors();
|
||||
for (const monitor of monitors) {
|
||||
await queue.addJob(monitor.id, monitor);
|
||||
}
|
||||
const workerStats = await queue.getWorkerStats();
|
||||
await queue.scaleWorkers(workerStats);
|
||||
return queue;
|
||||
@@ -52,9 +57,25 @@ class JobQueue {
|
||||
const worker = new Worker(
|
||||
QUEUE_NAME,
|
||||
async (job) => {
|
||||
const response = await axios.get(job.data.url);
|
||||
// TODO create a check object and save it to the db
|
||||
console.log(response.status);
|
||||
// TODO Extract to service
|
||||
if (job.data.type === "ping") {
|
||||
const response = await ping.promise.probe(job.data.url);
|
||||
// TODO insert checks into DB
|
||||
if (response.alive === true) {
|
||||
console.log(`${job.data.url} is alive`);
|
||||
} else {
|
||||
console.log(`${job.data.url} is dead`);
|
||||
}
|
||||
} else if (job.data.type === "http") {
|
||||
const response = await axios.get(job.data.url);
|
||||
// TODO create a check object and save it to the db
|
||||
if (response.status >= 200 && response.status < 300) {
|
||||
// TODO insert checks into DB
|
||||
console.log(`${job.data.url} is alive`);
|
||||
} else {
|
||||
console.log(`${job.data.url} is dead`);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
connection,
|
||||
@@ -135,7 +156,9 @@ class JobQueue {
|
||||
await worker.close();
|
||||
} catch (error) {
|
||||
// Catch the error instead of throwing it
|
||||
console.error("Error closing worker", error);
|
||||
logger.error(errorMessages.JOB_QUEUE_WORKER_CLOSE, {
|
||||
service: SERVICE_NAME,
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -194,12 +217,12 @@ class JobQueue {
|
||||
every: monitor.interval,
|
||||
});
|
||||
if (deleted) {
|
||||
logger.info("Job removed from queue", {
|
||||
logger.info(successMessages.JOB_QUEUE_DELETE_JOB, {
|
||||
service: SERVICE_NAME,
|
||||
jobId: monitor.id,
|
||||
});
|
||||
} else {
|
||||
logger.error("Job not found in queue", {
|
||||
logger.error(errorMessages.JOB_QUEUE_DELETE_JOB, {
|
||||
service: SERVICE_NAME,
|
||||
jobId: monitor.id,
|
||||
});
|
||||
@@ -215,12 +238,23 @@ class JobQueue {
|
||||
*/
|
||||
async obliterate() {
|
||||
try {
|
||||
const jobs = await this.getJobs();
|
||||
for (const job of jobs) {
|
||||
await this.queue.remove(job.id);
|
||||
}
|
||||
console.log(jobs);
|
||||
await this.queue.obliterate();
|
||||
logger.info(successMessages.JOB_QUEUE_OBLITERATE, {
|
||||
service: SERVICE_NAME,
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error(errorMessages.JOB_QUEUE_OBLITERATE);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
//TODO Cleanup Queue on shutdown
|
||||
}
|
||||
|
||||
module.exports = JobQueue;
|
||||
@@ -0,0 +1,81 @@
|
||||
const errorMessages = {
|
||||
// General Errors:
|
||||
FRIENDLY_ERROR: "Something went wrong...",
|
||||
UNKNOWN_ERROR: "An unknown error occurred",
|
||||
// Auth Controller
|
||||
UNAUTHORIZED: "Unauthorized access",
|
||||
|
||||
//Error handling middleware
|
||||
UNKNOWN_SERVICE: "Unknown service",
|
||||
NO_AUTH_TOKEN: "No auth token provided",
|
||||
INVALID_AUTH_TOKEN: "Invalid auth token",
|
||||
|
||||
//Onwership Middleware
|
||||
VERIFY_OWNER_NOT_FOUND: "Document not found",
|
||||
VERIFY_OWNER_UNAUTHORIZED: "Unauthorized access",
|
||||
|
||||
//DB Errors
|
||||
DB_USER_EXISTS: "User already exists",
|
||||
DB_USER_NOT_FOUND: "User not found",
|
||||
DB_TOKEN_NOT_FOUND: "Token not found",
|
||||
DB_RESET_PASSWORD_BAD_MATCH:
|
||||
"New password must be different from old password",
|
||||
DB_FIND_MONTIOR_BY_ID: (monitorId) =>
|
||||
`Monitor with id ${monitorId} not found`,
|
||||
DB_DELETE_CHECKS: (monitorId) =>
|
||||
`No checks found for monitor with id ${monitorId}`,
|
||||
DB_DELETE_ALERT: (alertId) => `Alert with id ${alertId} not found`,
|
||||
|
||||
//Auth errors
|
||||
AUTH_INCORRECT_PASSWORD: "Incorrect password",
|
||||
AUTH_UNAUTHORIZED: "Unauthorized access",
|
||||
|
||||
// Monitor Errros
|
||||
MONITOR_GET_BY_ID: "Monitor not found",
|
||||
MONITOR_GET_BY_USER_ID: "No monitors found for user",
|
||||
|
||||
// Job Queue Errors
|
||||
JOB_QUEUE_WORKER_CLOSE: "Error closing worker",
|
||||
JOB_QUEUE_DELETE_JOB: "Job not found in queue",
|
||||
JOB_QUEUE_OBLITERATE: "Error obliterating queue",
|
||||
};
|
||||
|
||||
const successMessages = {
|
||||
//Alert Controller
|
||||
ALERT_CREATE: "Alert created successfully",
|
||||
ALERT_GET_BY_USER: "Got alerts successfully",
|
||||
ALERT_GET_BY_MONITOR: "Got alerts by Monitor successfully",
|
||||
ALERT_GET_BY_ID: "Got alert by Id successfully",
|
||||
ALERT_EDIT: "Alert edited successfully",
|
||||
ALERT_DELETE: "Alert deleted successfully",
|
||||
|
||||
// Auth Controller
|
||||
AUTH_CREATE_USER: "User created successfully",
|
||||
AUTH_LOGIN_USER: "User logged in successfully",
|
||||
AUTH_UPDATE_USER: "User updated successfully",
|
||||
AUTH_CREATE_RECOVERY_TOKEN: "Recovery token created successfully",
|
||||
AUTH_VERIFY_RECOVERY_TOKEN: "Recovery token verified successfully",
|
||||
AUTH_RESET_PASSWORD: "Password reset successfully",
|
||||
|
||||
// Check Controller
|
||||
CHECK_CREATE: "Check created successfully",
|
||||
CHECK_GET: "Got checks successfully",
|
||||
CHECK_DELETE: "Checks deleted successfully",
|
||||
|
||||
//Monitor Controller
|
||||
MONITOR_GET_ALL: "Got all monitors successfully",
|
||||
MONTIOR_GET_BY_ID: "Got monitor by Id successfully",
|
||||
MONITOR_GET_BY_USER_ID: (userId) => `Got monitor for ${userId} successfully"`,
|
||||
MONITOR_CREATE: "Monitor created successfully",
|
||||
MONITOR_DELETE: "Monitor deleted successfully",
|
||||
MONITOR_EDIT: "Monitor edited successfully",
|
||||
|
||||
//Job Queue
|
||||
JOB_QUEUE_DELETE_JOB: "Job removed successfully",
|
||||
JOB_QUEUE_OBLITERATE: "Queue OBLITERATED!!!",
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
errorMessages,
|
||||
successMessages,
|
||||
};
|
||||
@@ -60,7 +60,8 @@ const monitorValidation = joi.object({
|
||||
userId: joi.string().required(),
|
||||
name: joi.string().required(),
|
||||
description: joi.string().required(),
|
||||
url: joi.string().uri().required(),
|
||||
type: joi.string().required(),
|
||||
url: joi.string().required(),
|
||||
isActive: joi.boolean(),
|
||||
interval: joi.number(),
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user