diff --git a/Client/src/Components/TabPanels/Account/TeamPanel.jsx b/Client/src/Components/TabPanels/Account/TeamPanel.jsx index 07bbf6e68..95df3e294 100644 --- a/Client/src/Components/TabPanels/Account/TeamPanel.jsx +++ b/Client/src/Components/TabPanels/Account/TeamPanel.jsx @@ -16,7 +16,7 @@ import { useEffect, useState } from "react"; import EditSvg from "../../../assets/icons/edit.svg?react"; import Field from "../../Inputs/Field"; import { credentials } from "../../../Validation/validation"; -import axiosInstance from "../../../Utils/axiosConfig"; +import { networkService } from "../../../main"; import { createToast } from "../../../Utils/toastUtils"; import { useSelector } from "react-redux"; import BasicTable from "../../BasicTable"; @@ -52,10 +52,7 @@ const TeamPanel = () => { useEffect(() => { const fetchTeam = async () => { try { - const response = await axiosInstance.get("/auth/users", { - headers: { Authorization: `Bearer ${authToken}` }, - }); - + const response = await networkService.getAllUsers(authToken); setMembers(response.data.data); } catch (error) { createToast({ @@ -179,13 +176,10 @@ const TeamPanel = () => { setErrors((prev) => ({ ...prev, email: error.details[0].message })); } else try { - await axiosInstance.post( - "/auth/invite", - { - email: toInvite.email, - role: toInvite.role, - }, - { headers: { Authorization: `Bearer ${authToken}` } } + await networkService.requestInvitationToken( + authToken, + toInvite.email, + toInvite.role ); closeInviteModal(); diff --git a/Client/src/Features/Auth/authSlice.js b/Client/src/Features/Auth/authSlice.js index 6da315fc4..fa1f0471e 100644 --- a/Client/src/Features/Auth/authSlice.js +++ b/Client/src/Features/Auth/authSlice.js @@ -1,4 +1,4 @@ -import axiosInstance from "../../Utils/axiosConfig"; +import { networkService } from "../../main"; import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { jwtDecode } from "jwt-decode"; @@ -14,7 +14,7 @@ export const register = createAsyncThunk( "auth/register", async (form, thunkApi) => { try { - const res = await axiosInstance.post("/auth/register", form); + const res = await networkService.registerUser(form); return res.data; } catch (error) { if (error.response.data) { @@ -31,7 +31,7 @@ export const register = createAsyncThunk( export const login = createAsyncThunk("auth/login", async (form, thunkApi) => { try { - const res = await axiosInstance.post(`/auth/login`, form); + const res = await networkService.loginUser(form); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -51,16 +51,13 @@ export const update = createAsyncThunk( const { authToken: token, localData: form } = data; const user = jwtDecode(token); try { - //1.5s delay to show loading spinner - await new Promise((resolve) => setTimeout(resolve, 1500)); - const fd = new FormData(); form.firstName && fd.append("firstName", form.firstName); form.lastName && fd.append("lastName", form.lastName); form.password && fd.append("password", form.password); form.newPassword && fd.append("newPassword", form.newPassword); if (form.file && form.file !== "") { - const imageResult = await axiosInstance.get(form.file, { + const imageResult = await networkService.get(form.file, { responseType: "blob", baseURL: "", }); @@ -69,12 +66,8 @@ export const update = createAsyncThunk( form.deleteProfileImage && fd.append("deleteProfileImage", form.deleteProfileImage); - const res = await axiosInstance.put(`/auth/user/${user._id}`, fd, { - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "multipart/form-data", - }, - }); + const res = await networkService.updateUser(token, user._id, fd); + return res.data; } catch (error) { if (error.response && error.response.data) { @@ -95,9 +88,7 @@ export const deleteUser = createAsyncThunk( const user = jwtDecode(data); try { - const res = await axiosInstance.delete(`/auth/user/${user._id}`, { - headers: { Authorization: `Bearer ${data}` }, - }); + const res = await networkService.deleteUser(data, user._id); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -116,7 +107,7 @@ export const forgotPassword = createAsyncThunk( "auth/forgotPassword", async (form, thunkApi) => { try { - const res = await axiosInstance.post("/auth/recovery/request", form); + const res = await networkService.forgotPassword(form); return res.data; } catch (error) { if (error.response.data) { @@ -136,13 +127,8 @@ export const setNewPassword = createAsyncThunk( async (data, thunkApi) => { const { token, form } = data; try { - await axiosInstance.post("/auth/recovery/validate", { - recoveryToken: token, - }); - const res = await axiosInstance.post("/auth/recovery/reset", { - ...form, - recoveryToken: token, - }); + await networkService.validateRecoveryToken(token); + const res = await networkService.setNewPassword(token, form); return res.data; } catch (error) { if (error.response.data) { diff --git a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js index 6af825a37..22e2b3324 100644 --- a/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js +++ b/Client/src/Features/PageSpeedMonitor/pageSpeedMonitorSlice.js @@ -1,6 +1,6 @@ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { jwtDecode } from "jwt-decode"; -import axiosInstance from "../../Utils/axiosConfig"; +import { networkService } from "../../main"; const initialState = { isLoading: false, monitors: [], @@ -13,32 +13,7 @@ export const createPageSpeed = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitor } = data; - - const res = await axiosInstance.post(`/monitors`, monitor, { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); - -export const getPageSpeedMonitors = createAsyncThunk( - "pageSpeedMonitors/getPageSpeedMonitors", - async (token, thunkApi) => { - try { - const res = await axiosInstance.get("/monitors"); + const res = await networkService.createMonitor(authToken, monitor); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -58,14 +33,16 @@ export const getPageSpeedByUserId = createAsyncThunk( async (token, thunkApi) => { const user = jwtDecode(token); try { - const res = await axiosInstance.get( - `/monitors/user/${user._id}?limit=25&type=pagespeed&sortOrder=desc`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } + const res = await networkService.getMonitorsByUserId( + token, + user._id, + 25, + ["pagespeed"], + null, + "desc", + false ); + return res.data; } catch (error) { if (error.response && error.response.data) { @@ -91,15 +68,10 @@ export const updatePageSpeed = createAsyncThunk( interval: monitor.interval, // notifications: monitor.notifications, }; - const res = await axiosInstance.put( - `/monitors/${monitor._id}`, - updatedFields, - { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - } + const res = await networkService.updateMonitor( + authToken, + monitor._id, + updatedFields ); return res.data; } catch (error) { @@ -120,12 +92,10 @@ export const deletePageSpeed = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitor } = data; - const res = await axiosInstance.delete(`/monitors/${monitor._id}`, { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - }); + const res = await networkService.deleteMonitorById( + authToken, + monitor._id + ); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -153,25 +123,6 @@ const pageSpeedMonitorSlice = createSlice({ }, extraReducers: (builder) => { builder - // ***************************************************** - // All Monitors - // ***************************************************** - .addCase(getPageSpeedMonitors.pending, (state) => { - state.isLoading = true; - }) - .addCase(getPageSpeedMonitors.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - state.monitors = action.payload.data; - }) - .addCase(getPageSpeedMonitors.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Getting montiors failed"; - }) // ***************************************************** // Monitors by userId // ***************************************************** diff --git a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js index 27d616e1e..9ab7d385b 100644 --- a/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js +++ b/Client/src/Features/UptimeMonitors/uptimeMonitorsSlice.js @@ -1,6 +1,6 @@ import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { jwtDecode } from "jwt-decode"; -import axiosInstance from "../../Utils/axiosConfig"; +import { networkService } from "../../main"; const initialState = { isLoading: false, monitors: [], @@ -13,32 +13,7 @@ export const createUptimeMonitor = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitor } = data; - - const res = await axiosInstance.post(`/monitors`, monitor, { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - }); - return res.data; - } catch (error) { - if (error.response && error.response.data) { - return thunkApi.rejectWithValue(error.response.data); - } - const payload = { - status: false, - msg: error.message ? error.message : "Unknown error", - }; - return thunkApi.rejectWithValue(payload); - } - } -); - -export const getUptimeMonitors = createAsyncThunk( - "monitors/getMonitors", - async (token, thunkApi) => { - try { - const res = await axiosInstance.get("/monitors"); + const res = await networkService.createMonitor(authToken, monitor); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -58,13 +33,14 @@ export const getUptimeMonitorsByUserId = createAsyncThunk( async (token, thunkApi) => { const user = jwtDecode(token); try { - const res = await axiosInstance.get( - `/monitors/user/${user._id}?limit=25&type=http&type=ping&sortOrder=desc&normalize=true`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } + const res = await networkService.getMonitorsByUserId( + token, + user._id, + 25, + ["http", "ping"], + null, + "desc", + true ); return res.data; } catch (error) { @@ -91,15 +67,10 @@ export const updateUptimeMonitor = createAsyncThunk( interval: monitor.interval, notifications: monitor.notifications, }; - const res = await axiosInstance.put( - `/monitors/${monitor._id}`, - updatedFields, - { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - } + const res = await networkService.updateMonitor( + authToken, + monitor._id, + updatedFields ); return res.data; } catch (error) { @@ -120,12 +91,10 @@ export const deleteUptimeMonitor = createAsyncThunk( async (data, thunkApi) => { try { const { authToken, monitor } = data; - const res = await axiosInstance.delete(`/monitors/${monitor._id}`, { - headers: { - Authorization: `Bearer ${authToken}`, - "Content-Type": "application/json", - }, - }); + const res = await networkService.deleteMonitorById( + authToken, + monitor._id + ); return res.data; } catch (error) { if (error.response && error.response.data) { @@ -153,25 +122,6 @@ const uptimeMonitorsSlice = createSlice({ }, extraReducers: (builder) => { builder - // ***************************************************** - // All Monitors - // ***************************************************** - .addCase(getUptimeMonitors.pending, (state) => { - state.isLoading = true; - }) - .addCase(getUptimeMonitors.fulfilled, (state, action) => { - state.isLoading = false; - state.success = action.payload.success; - state.msg = action.payload.msg; - state.monitors = action.payload.data; - }) - .addCase(getUptimeMonitors.rejected, (state, action) => { - state.isLoading = false; - state.success = false; - state.msg = action.payload - ? action.payload.msg - : "Getting uptime monitors failed"; - }) // ***************************************************** // Monitors by userId // ***************************************************** diff --git a/Client/src/HOC/withAdminCheck.jsx b/Client/src/HOC/withAdminCheck.jsx index 6c5087926..30a767f36 100644 --- a/Client/src/HOC/withAdminCheck.jsx +++ b/Client/src/HOC/withAdminCheck.jsx @@ -1,15 +1,16 @@ import React, { useEffect } from "react"; import { useNavigate } from "react-router-dom"; -import axiosInstance from "../Utils/axiosConfig"; + import { logger } from "../Utils/Logger"; +import { networkService } from "../main"; const withAdminCheck = (WrappedComponent) => { const WithAdminCheck = (props) => { const navigate = useNavigate(); useEffect(() => { - axiosInstance - .get("/auth/users/admin") + networkService + .doesAdminExist() .then((response) => { if (response.data.data === true) { navigate("/login"); diff --git a/Client/src/Pages/Auth/Login.jsx b/Client/src/Pages/Auth/Login.jsx index d5757e0cd..79e8d7e12 100644 --- a/Client/src/Pages/Auth/Login.jsx +++ b/Client/src/Pages/Auth/Login.jsx @@ -7,7 +7,7 @@ import { login } from "../../Features/Auth/authSlice"; import { useDispatch, useSelector } from "react-redux"; import { createToast } from "../../Utils/toastUtils"; import Button from "../../Components/Button"; -import axiosInstance from "../../Utils/axiosConfig"; +import { networkService } from "../../main"; import Field from "../../Components/Inputs/Field"; import background from "../../assets/Images/background_pattern_decorative.png"; import Logo from "../../assets/icons/bwu-icon.svg?react"; @@ -276,8 +276,8 @@ const Login = () => { navigate("/monitors"); return; } - axiosInstance - .get("/auth/users/admin") + networkService + .doesAdminExist() .then((response) => { if (response.data.data === false) { navigate("/register"); diff --git a/Client/src/Pages/Auth/Register/Register.jsx b/Client/src/Pages/Auth/Register/Register.jsx index 10e81c176..225fb16f0 100644 --- a/Client/src/Pages/Auth/Register/Register.jsx +++ b/Client/src/Pages/Auth/Register/Register.jsx @@ -15,7 +15,7 @@ import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; import Check from "../../../Components/Check/Check"; import Button from "../../../Components/Button"; import Field from "../../../Components/Inputs/Field"; -import axiosInstance from "../../../Utils/axiosConfig"; +import { networkService } from "../../../main"; import "../index.css"; import { logger } from "../../../Utils/Logger"; @@ -398,9 +398,7 @@ const Register = ({ isAdmin }) => { const fetchInvite = async () => { if (token !== undefined) { try { - const res = await axiosInstance.post(`/auth/invite/verify`, { - token, - }); + const res = await networkService.verifyInvitationToken(token); const { role, email } = res.data.data; setForm({ ...form, email, role }); } catch (error) { diff --git a/Client/src/Pages/Incidents/IncidentTable/index.jsx b/Client/src/Pages/Incidents/IncidentTable/index.jsx index 473062bbc..705daa70c 100644 --- a/Client/src/Pages/Incidents/IncidentTable/index.jsx +++ b/Client/src/Pages/Incidents/IncidentTable/index.jsx @@ -18,7 +18,7 @@ import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded"; import { useState, useEffect } from "react"; import { useSelector } from "react-redux"; -import axiosInstance from "../../../Utils/axiosConfig"; +import { networkService } from "../../../main"; import { StatusLabel } from "../../../Components/Label"; import { logger } from "../../../Utils/Logger"; @@ -44,17 +44,30 @@ const IncidentTable = ({ monitors, selectedMonitor, filter }) => { return; } try { - let url = `/checks/${selectedMonitor}?sortOrder=desc&filter=${filter}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`; - + let res; if (selectedMonitor === "0") { - url = `/checks/user/${user._id}?sortOrder=desc&filter=${filter}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`; + res = await networkService.getChecksByUser( + authToken, + user._id, + "desc", + null, + null, + filter, + paginationController.page, + paginationController.rowsPerPage + ); + } else { + res = await networkService.getChecksByMonitor( + authToken, + selectedMonitor, + "desc", + null, + null, + filter, + paginationController.page, + paginationController.rowsPerPage + ); } - - const res = await axiosInstance.get(url, { - headers: { - Authorization: `Bearer ${authToken}`, - }, - }); setChecks(res.data.data.checks); setChecksCount(res.data.data.checksCount); } catch (error) { diff --git a/Client/src/Pages/Incidents/index.jsx b/Client/src/Pages/Incidents/index.jsx index f9d53f7c0..34768f7f9 100644 --- a/Client/src/Pages/Incidents/index.jsx +++ b/Client/src/Pages/Incidents/index.jsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { useSelector } from "react-redux"; import { ButtonGroup, Stack, Skeleton, Typography } from "@mui/material"; import Button from "../../Components/Button"; -import axiosInstance from "../../Utils/axiosConfig"; +import { networkService } from "../../main"; import { useTheme } from "@emotion/react"; import Select from "../../Components/Inputs/Select"; import IncidentTable from "./IncidentTable"; @@ -48,13 +48,14 @@ const Incidents = () => { useEffect(() => { const fetchMonitors = async () => { setLoading(true); - const res = await axiosInstance.get( - `/monitors/user/${authState.user._id}?status=false&limit=1`, - { - headers: { - Authorization: `Bearer ${authState.authToken}`, - }, - } + const res = await networkService.getMonitorsByUserId( + authState.authToken, + authState.user._id, + 1, + null, + null, + null, + null ); // Reduce to a lookup object for 0(1) lookup diff --git a/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx b/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx index f7e03171f..da4a0cb1e 100644 --- a/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx +++ b/Client/src/Pages/Monitors/Details/PaginationTable/index.jsx @@ -13,7 +13,7 @@ import { import { useState, useEffect } from "react"; import { useSelector } from "react-redux"; -import axiosInstance from "../../../../Utils/axiosConfig"; +import { networkService } from "../../../../main"; import { StatusLabel } from "../../../../Components/Label"; import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded"; import ArrowForwardRoundedIcon from "@mui/icons-material/ArrowForwardRounded"; @@ -38,13 +38,15 @@ const PaginationTable = ({ monitorId, dateRange }) => { useEffect(() => { const fetchPage = async () => { try { - const res = await axiosInstance.get( - `/checks/${monitorId}?sortOrder=desc&dateRange=${dateRange}&page=${paginationController.page}&rowsPerPage=${paginationController.rowsPerPage}`, - { - headers: { - Authorization: `Bearer ${authToken}`, - }, - } + const res = await networkService.getChecksByMonitor( + authToken, + monitorId, + "desc", + null, + dateRange, + null, + paginationController.page, + paginationController.rowsPerPage ); setChecks(res.data.data.checks); setChecksCount(res.data.data.checksCount); diff --git a/Client/src/Pages/Monitors/Details/index.jsx b/Client/src/Pages/Monitors/Details/index.jsx index 92969ce64..85f3a0bb8 100644 --- a/Client/src/Pages/Monitors/Details/index.jsx +++ b/Client/src/Pages/Monitors/Details/index.jsx @@ -3,7 +3,7 @@ import PropTypes from "prop-types"; import { Box, Skeleton, Stack, Typography, useTheme } from "@mui/material"; import { useSelector } from "react-redux"; import { useNavigate, useParams } from "react-router-dom"; -import axiosInstance from "../../../Utils/axiosConfig"; +import { networkService } from "../../../main"; import MonitorDetailsAreaChart from "../../../Components/Charts/MonitorDetailsAreaChart"; import ButtonGroup from "@mui/material/ButtonGroup"; import Button from "../../../Components/Button"; @@ -122,13 +122,14 @@ const DetailsPage = () => { const fetchMonitor = useCallback(async () => { try { - const res = await axiosInstance.get( - `/monitors/stats/${monitorId}?dateRange=${dateRange}&numToDisplay=50&normalize=true`, - { - headers: { - Authorization: `Bearer ${authToken}`, - }, - } + const res = await networkService.getStatsByMonitorId( + authToken, + monitorId, + null, + null, + dateRange, + 50, + true ); setMonitor(res.data.data); } catch (error) { @@ -143,13 +144,9 @@ const DetailsPage = () => { useEffect(() => { const fetchCertificate = async () => { - const res = await axiosInstance.get( - `/monitors/certificate/${monitorId}`, - { - headers: { - Authorization: `Bearer ${authToken}`, - }, - } + const res = await networkService.getCertificateExpiry( + authToken, + monitorId ); setCertificateExpiry(res.data.data.certificateDate); }; diff --git a/Client/src/Pages/PageSpeed/Details/index.jsx b/Client/src/Pages/PageSpeed/Details/index.jsx index 4ce031973..adcc51a3c 100644 --- a/Client/src/Pages/PageSpeed/Details/index.jsx +++ b/Client/src/Pages/PageSpeed/Details/index.jsx @@ -9,7 +9,7 @@ import { formatDuration, formatDurationRounded, } from "../../../Utils/timeUtils"; -import axiosInstance from "../../../Utils/axiosConfig"; +import { networkService } from "../../../main"; import Button from "../../../Components/Button"; import SettingsIcon from "../../../assets/icons/settings-bold.svg?react"; import LastCheckedIcon from "../../../assets/icons/calendar-check.svg?react"; @@ -199,15 +199,15 @@ const PageSpeedDetails = () => { useEffect(() => { const fetchMonitor = async () => { try { - const res = await axiosInstance.get( - `/monitors/stats/${monitorId}?sortOrder=desc&limit=50`, - { - headers: { - Authorization: `Bearer ${authToken}`, - }, - } + const res = await networkService.getStatsByMonitorId( + authToken, + monitorId, + "desc", + 50, + null, + null, + null ); - setMonitor(res?.data?.data ?? {}); setAudits(res?.data?.data?.checks?.[0]?.audits ?? []); } catch (error) { diff --git a/Client/src/Utils/NetworkService.js b/Client/src/Utils/NetworkService.js new file mode 100644 index 000000000..21f1c1845 --- /dev/null +++ b/Client/src/Utils/NetworkService.js @@ -0,0 +1,434 @@ +import axios from "axios"; +import { clearAuthState } from "../Features/Auth/authSlice"; +const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; + +class NetworkService { + constructor(store) { + this.store = store; + this.axiosInstance = axios.create({ baseURL: BASE_URL }); + this.axiosInstance.interceptors.response.use( + (response) => response, + (error) => { + console.error(error); + if (error.response && error.response.status === 401) { + console.log("Invalid token revoked"); + networkService; + } + return Promise.reject(error); + } + ); + } + + /** + * + * ************************************ + * Create a new monitor + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {Object} monitor - The monitor object to be sent in the request body. + * @returns {Promise} The response from the axios POST request. + */ + async createMonitor(authToken, monitor) { + return this.axiosInstance.post(`/monitors`, monitor, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + } + + /** + * ************************************ + * Get all uptime monitors for a user + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} userId - The ID of the user whose monitors are to be retrieved. + * @param {number} [limit] - The maximum number of monitors to retrieve. + * @param {Array} [types] - The types of monitors to retrieve. + * @param {string} [status] - The status of the monitors to retrieve. + * @param {string} [sortOrder] - The order in which to sort the retrieved monitors. + * @param {boolean} [normalize] - Whether to normalize the retrieved monitors. + * @returns {Promise} The response from the axios GET request. + */ + async getMonitorsByUserId( + authToken, + userId, + limit, + types, + status, + sortOrder, + normalize + ) { + const params = new URLSearchParams(); + + if (limit) params.append("limit", limit); + if (types) { + types.forEach((type) => { + params.append("type", type); + }); + } + if (status) params.append("status", status); + if (sortOrder) params.append("sortOrder", sortOrder); + if (normalize) params.append("normalize", normalize); + + return this.axiosInstance.get( + `/monitors/user/${userId}?${params.toString()}`, + { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + } + ); + } + + /** + * ************************************ + * Get stats for a monitor + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} monitorId - The ID of the monitor whose statistics are to be retrieved. + * @param {string} [sortOrder] - The order in which to sort the retrieved statistics. + * @param {number} [limit] - The maximum number of statistics to retrieve. + * @param {string} [dateRange] - The date range for which to retrieve statistics. + * @param {number} [numToDisplay] - The number of statistics to display. + * @param {boolean} [normalize] - Whether to normalize the retrieved statistics. + * @returns {Promise} The response from the axios GET request. + */ + async getStatsByMonitorId( + authToken, + monitorId, + sortOrder, + limit, + dateRange, + numToDisplay, + normalize + ) { + const params = new URLSearchParams(); + if (sortOrder) params.append("sortOrder", sortOrder); + if (limit) params.append("limit", limit); + if (dateRange) params.append("dateRange", dateRange); + if (numToDisplay) params.append("numToDisplay", numToDisplay); + if (normalize) params.append("normalize", normalize); + + return this.axiosInstance.get( + `/monitors/stats/${monitorId}?${params.toString()}`, + { + headers: { + Authorization: `Bearer ${authToken}`, + }, + } + ); + } + + /** + * ************************************ + * Updates a single monitor + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} monitorId - The ID of the monitor to be updated. + * @param {Object} updatedFields - The fields to be updated for the monitor. + * @returns {Promise} The response from the axios PUT request. + */ + async updateMonitor(authToken, monitorId, updatedFields) { + return this.axiosInstance.put(`/monitors/${monitorId}`, updatedFields, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + } + + /** + * ************************************ + * Deletes a single monitor by its ID + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} monitorId - The ID of the monitor to be deleted. + * @returns {Promise} The response from the axios DELETE request. + */ + async deleteMonitorById(authToken, monitorId) { + return this.axiosInstance.delete(`/monitors/${monitorId}`, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + } + + /** + * ************************************ + * Gets the certificate expiry for a monitor + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} monitorId - The ID of the monitor whose certificate expiry is to be retrieved. + * @returns {Promise} The response from the axios GET request. + * + */ + async getCertificateExpiry(authToken, monitorId) { + return this.axiosInstance.get(`/monitors/certificate/${monitorId}`, { + headers: { + Authorization: `Bearer ${authToken}`, + }, + }); + } + + /** + * ************************************ + * Registers a new user + * ************************************ + * + * @async + * @param {Object} form - The form data for the new user to be registered. + * @returns {Promise} The response from the axios POST request. + */ + async registerUser(form) { + return this.axiosInstance.post(`/auth/register`, form); + } + + /** + * ************************************ + * Logs in a user + * ************************************ + * + * @async + * @param {Object} form - The form data for the user to be logged in. + * @returns {Promise} The response from the axios POST request. + * + */ + async loginUser(form) { + return this.axiosInstance.post(`/auth/login`, form); + } + + /** + * ************************************ + * Updates a user + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} userId - The ID of the user to be updated. + * @param {Object} form - The form data for the user to be updated. + * @returns {Promise} The response from the axios PUT request. + * + */ + async updateUser(authToken, userId, form) { + return this.axiosInstance.put(`/auth/user/${userId}`, form, { + headers: { + Authorization: `Bearer ${authToken}`, + "Content-Type": "application/json", + }, + }); + } + + /** + * ************************************ + * Forgot password request + * ************************************ + * + * @async + * @param {Object} form - The form data for the password recovery request. + * @returns {Promise} The response from the axios POST request. + * + */ + async forgotPassword(form) { + return this.axiosInstance.post(`/auth/recovery/request`, form); + } + + /** + * ************************************ + * Validates a recovery token + * ************************************ + * + * @async + * @param {string} recoveryToken - The recovery token to be validated. + * @returns {Promise} The response from the axios POST request. + * + */ + async validateRecoveryToken(recoveryToken) { + return this.axiosInstance.post("/auth/recovery/validate", { + recoveryToken, + }); + } + + /** + * ************************************ + * Requests password recovery + * ************************************ + * + * @async + * @param {Object} form - The form data for the password recovery request. + * @returns {Promise} The response from the axios POST request. + * + */ + async setNewPassword(recoveryToken, form) { + return this.axiosInstance.post("/auth/recovery/reset", { + ...form, + recoveryToken, + }); + } + + /** + * ************************************ + * Checks if an admin user exists + * ************************************ + * + * @async + * @returns {Promise} The response from the axios GET request. + * + */ + async doesAdminExist() { + return this.axiosInstance.get("/auth/users/admin"); + } + + /** + * ************************************ + * Get all users + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @returns {Promise} The response from the axios GET request. + * + */ + async getAllUsers(authToken) { + return this.axiosInstance.get("/auth/users", { + headers: { Authorization: `Bearer ${authToken}` }, + }); + } + + /** + * ************************************ + * Requests an invitation token + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} email - The email of the user to be invited. + * @param {string} role - The role of the user to be invited. + * @returns {Promise} The response from the axios POST request. + * + */ + async requestInvitationToken(authToken, email, role) { + return this.axiosInstance.post( + `/auth/invite`, + { email, role }, + { + headers: { Authorization: `Bearer ${authToken}` }, + } + ); + } + + /** + * ************************************ + * Verifies an invitation token + * ************************************ + * + * @async + * @param {string} token - The invitation token to be verified. + * @returns {Promise} The response from the axios POST request. + * + */ + async verifyInvitationToken(token) { + return this.axiosInstance.post(`/auth/invite/verify`, { + token, + }); + } + + /** + * ************************************ + * Get all checks for a given monitor + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} monitorId - The ID of the monitor. + * @param {string} sortOrder - The order in which to sort the checks. + * @param {number} limit - The maximum number of checks to retrieve. + * @param {string} dateRange - The range of dates for which to retrieve checks. + * @param {string} filter - The filter to apply to the checks. + * @param {number} page - The page number to retrieve in a paginated list. + * @param {number} rowsPerPage - The number of rows per page in a paginated list. + * @returns {Promise} The response from the axios GET request. + * + */ + + async getChecksByMonitor( + authToken, + monitorId, + sortOrder, + limit, + dateRange, + filter, + page, + rowsPerPage + ) { + const params = new URLSearchParams(); + if (sortOrder) params.append("sortOrder", sortOrder); + if (limit) params.append("limit", limit); + if (dateRange) params.append("dateRange", dateRange); + if (filter) params.append("filter", filter); + if (page) params.append("page", page); + if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); + + return this.axiosInstance.get(`/checks/${monitorId}?${params.toString()}`, { + headers: { Authorization: `Bearer ${authToken}` }, + }); + } + + /** + * ************************************ + * Get all checks for a given user + * ************************************ + * + * @async + * @param {string} authToken - The authorization token to be used in the request header. + * @param {string} userId - The ID of the user. + * @param {string} sortOrder - The order in which to sort the checks. + * @param {number} limit - The maximum number of checks to retrieve. + * @param {string} dateRange - The range of dates for which to retrieve checks. + * @param {string} filter - The filter to apply to the checks. + * @param {number} page - The page number to retrieve in a paginated list. + * @param {number} rowsPerPage - The number of rows per page in a paginated list. + * @returns {Promise} The response from the axios GET request. + * + */ + async getChecksByUser( + authToken, + userId, + sortOrder, + limit, + dateRange, + filter, + page, + rowsPerPage + ) { + const params = new URLSearchParams(); + if (sortOrder) params.append("sortOrder", sortOrder); + if (limit) params.append("limit", limit); + if (dateRange) params.append("dateRange", dateRange); + if (filter) params.append("filter", filter); + if (page) params.append("page", page); + if (rowsPerPage) params.append("rowsPerPage", rowsPerPage); + return this.axiosInstance.get( + `/checks/user/${userId}?${params.toString()}`, + { + headers: { Authorization: `Bearer ${authToken}` }, + } + ); + } +} + +export default NetworkService; diff --git a/Client/src/Utils/axiosConfig.js b/Client/src/Utils/axiosConfig.js deleted file mode 100644 index 8d3040028..000000000 --- a/Client/src/Utils/axiosConfig.js +++ /dev/null @@ -1,27 +0,0 @@ -import axios from "axios"; -import { clearAuthState } from "../Features/Auth/authSlice"; -const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL; - -let store; - -export const injectStore = (s) => { - store = s; -}; - -const axiosInstance = axios.create({ - baseURL: BASE_URL, -}); - -axiosInstance.interceptors.response.use( - (response) => response, - (error) => { - console.error(error); - if (error.response && error.response.status === 401) { - console.log("Invalid token revoked"); - store.dispatch(clearAuthState()); - } - return Promise.reject(error); - } -); - -export default axiosInstance; diff --git a/Client/src/main.jsx b/Client/src/main.jsx index f9fa3d640..00cec7d14 100644 --- a/Client/src/main.jsx +++ b/Client/src/main.jsx @@ -8,10 +8,8 @@ import { ThemeProvider } from "@mui/material"; import { Provider } from "react-redux"; import { persistor, store } from "./store"; import { PersistGate } from "redux-persist/integration/react"; -import { injectStore } from "./Utils/axiosConfig.js"; - -injectStore(store); - +import NetworkService from "./Utils/NetworkService.js"; +export const networkService = new NetworkService(store); ReactDOM.createRoot(document.getElementById("root")).render( diff --git a/Server/controllers/authController.js b/Server/controllers/authController.js index 93c83c6b8..4b5f51121 100644 --- a/Server/controllers/authController.js +++ b/Server/controllers/authController.js @@ -450,6 +450,7 @@ const deleteUserController = async (req, res, next) => { } // 1. Find all the monitors associated with the user id + const monitors = await req.db.getMonitorsByUserId({ params: { userId: _id }, }); @@ -459,6 +460,7 @@ const deleteUserController = async (req, res, next) => { await Promise.all( monitors.map(async (monitor) => { await req.jobQueue.deleteJob(monitor); + await req.db.deleteChecks(monitor._id); await req.db.deleteAlertByMonitorId(monitor._id); await req.db.deletePageSpeedChecksByMonitorId(monitor._id); diff --git a/Server/db/mongo/modules/monitorModule.js b/Server/db/mongo/modules/monitorModule.js index 0fc56c755..4d84700ca 100644 --- a/Server/db/mongo/modules/monitorModule.js +++ b/Server/db/mongo/modules/monitorModule.js @@ -294,7 +294,7 @@ const getMonitorById = async (monitorId) => { */ const getMonitorsByUserId = async (req, res) => { try { - let { limit, type, status, sortOrder, normalize } = req.query; + let { limit, type, status, sortOrder, normalize } = req.query || {}; const monitorQuery = { userId: req.params.userId }; if (type !== undefined) { diff --git a/Server/db/mongo/modules/recoveryModule.js b/Server/db/mongo/modules/recoveryModule.js index c1cc23b94..b643a1664 100644 --- a/Server/db/mongo/modules/recoveryModule.js +++ b/Server/db/mongo/modules/recoveryModule.js @@ -1,3 +1,4 @@ +const UserModel = require("../../../models/user"); const RecoveryToken = require("../../../models/RecoveryToken"); const crypto = require("crypto"); const { errorMessages } = require("../../../utils/messages"); diff --git a/Server/index.js b/Server/index.js index 2cb941ed4..9a5f1797f 100644 --- a/Server/index.js +++ b/Server/index.js @@ -19,7 +19,7 @@ const JobQueue = require("./service/jobQueue"); const NetworkService = require("./service/networkService"); const EmailService = require("./service/emailService"); const PageSpeedService = require("./service/pageSpeedService"); - +const SERVICE_NAME = "Server"; let cleaningUp = false; // Need to wrap server setup in a function to handle async nature of JobQueue diff --git a/Server/routes/checkRoute.js b/Server/routes/checkRoute.js index 69aa682af..4bba24e79 100644 --- a/Server/routes/checkRoute.js +++ b/Server/routes/checkRoute.js @@ -15,12 +15,12 @@ router.get( checkController.getChecks ); +router.get("/user/:userId", checkController.getUserChecks); + router.delete( "/:monitorId", verifyOwnership(Monitor, "monitorId"), checkController.deleteChecks ); -router.get("/user/:userId", checkController.getUserChecks); - module.exports = router;