mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-06 17:59:40 -06:00
Merge pull request #2669 from bluewave-labs/feat/service-refactor
feat: service refactor
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import InviteToken from "../../models/InviteToken.js";
|
||||
import crypto from "crypto";
|
||||
import ServiceRegistry from "../../../service/serviceRegistry.js";
|
||||
import StringService from "../../../service/stringService.js";
|
||||
import ServiceRegistry from "../../../service/system/serviceRegistry.js";
|
||||
import StringService from "../../../service/system/stringService.js";
|
||||
|
||||
const SERVICE_NAME = "inviteModule";
|
||||
/**
|
||||
|
||||
@@ -4,8 +4,8 @@ import Check from "../../models/Check.js";
|
||||
import PageSpeedCheck from "../../models/PageSpeedCheck.js";
|
||||
import HardwareCheck from "../../models/HardwareCheck.js";
|
||||
import { NormalizeData, NormalizeDataUptimeDetails } from "../../../utils/dataUtils.js";
|
||||
import ServiceRegistry from "../../../service/serviceRegistry.js";
|
||||
import StringService from "../../../service/stringService.js";
|
||||
import ServiceRegistry from "../../../service/system/serviceRegistry.js";
|
||||
import StringService from "../../../service/system/stringService.js";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import UserModel from "../../models/User.js";
|
||||
import RecoveryToken from "../../models/RecoveryToken.js";
|
||||
import serviceRegistry from "../../../service/serviceRegistry.js";
|
||||
import StringService from "../../../service/stringService.js";
|
||||
import crypto from "crypto";
|
||||
import serviceRegistry from "../../../service/system/serviceRegistry.js";
|
||||
import StringService from "../../../service/system/stringService.js";
|
||||
|
||||
const SERVICE_NAME = "recoveryModule";
|
||||
|
||||
@@ -53,7 +54,7 @@ const resetPassword = async (password, candidateToken) => {
|
||||
const newPassword = password;
|
||||
|
||||
// Validate token again
|
||||
const recoveryToken = await validateRecoveryToken(req, res);
|
||||
const recoveryToken = await validateRecoveryToken(candidateToken);
|
||||
const user = await UserModel.findOne({ email: recoveryToken.email });
|
||||
|
||||
if (user === null) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import StatusPage from "../../models/StatusPage.js";
|
||||
import { NormalizeData } from "../../../utils/dataUtils.js";
|
||||
import ServiceRegistry from "../../../service/serviceRegistry.js";
|
||||
import StringService from "../../../service/stringService.js";
|
||||
import ServiceRegistry from "../../../service/system/serviceRegistry.js";
|
||||
import StringService from "../../../service/system/stringService.js";
|
||||
|
||||
const SERVICE_NAME = "statusPageModule";
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ import { GenerateAvatarImage } from "../../../utils/imageProcessing.js";
|
||||
|
||||
const DUPLICATE_KEY_CODE = 11000; // MongoDB error code for duplicate key
|
||||
import { ParseBoolean } from "../../../utils/utils.js";
|
||||
import ServiceRegistry from "../../../service/serviceRegistry.js";
|
||||
import StringService from "../../../service/stringService.js";
|
||||
import ServiceRegistry from "../../../service/system/serviceRegistry.js";
|
||||
import StringService from "../../../service/system/stringService.js";
|
||||
const SERVICE_NAME = "userModule";
|
||||
|
||||
const checkSuperadmin = async () => {
|
||||
|
||||
@@ -47,58 +47,58 @@ import DiagnosticRoutes from "./routes/diagnosticRoute.js";
|
||||
import DiagnosticController from "./controllers/diagnosticController.js";
|
||||
|
||||
//JobQueue service and dependencies
|
||||
import JobQueue from "./service/JobQueue/JobQueue.js";
|
||||
import JobQueueHelper from "./service/JobQueue/JobQueueHelper.js";
|
||||
import JobQueue from "./service/infrastructure/JobQueue/JobQueue.js";
|
||||
import JobQueueHelper from "./service/infrastructure/JobQueue/JobQueueHelper.js";
|
||||
import { Queue, Worker } from "bullmq";
|
||||
|
||||
import PulseQueue from "./service/PulseQueue/PulseQueue.js";
|
||||
import PulseQueueHelper from "./service/PulseQueue/PulseQueueHelper.js";
|
||||
import PulseQueue from "./service/infrastructure/PulseQueue/PulseQueue.js";
|
||||
import PulseQueueHelper from "./service/infrastructure/PulseQueue/PulseQueueHelper.js";
|
||||
|
||||
import SuperSimpleQueue from "./service/SuperSimpleQueue/SuperSimpleQueue.js";
|
||||
import SuperSimpleQueueHelper from "./service/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||
import SuperSimpleQueue from "./service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js";
|
||||
import SuperSimpleQueueHelper from "./service/infrastructure/SuperSimpleQueue/SuperSimpleQueueHelper.js";
|
||||
|
||||
import UserService from "./service/userService.js";
|
||||
import UserService from "./service/business/userService.js";
|
||||
|
||||
//Network service and dependencies
|
||||
import NetworkService from "./service/networkService.js";
|
||||
import NetworkService from "./service/infrastructure/networkService.js";
|
||||
import axios from "axios";
|
||||
import ping from "ping";
|
||||
import http from "http";
|
||||
import Docker from "dockerode";
|
||||
import net from "net";
|
||||
// Email service and dependencies
|
||||
import EmailService from "./service/emailService.js";
|
||||
import EmailService from "./service/infrastructure/emailService.js";
|
||||
import nodemailer from "nodemailer";
|
||||
import pkg from "handlebars";
|
||||
const { compile } = pkg;
|
||||
import mjml2html from "mjml";
|
||||
|
||||
// Settings Service and dependencies
|
||||
import SettingsService from "./service/settingsService.js";
|
||||
import SettingsService from "./service/system/settingsService.js";
|
||||
import AppSettings from "./db/models/AppSettings.js";
|
||||
|
||||
// Status Service and dependencies
|
||||
import StatusService from "./service/statusService.js";
|
||||
import StatusService from "./service/infrastructure/statusService.js";
|
||||
|
||||
// Notification Service and dependencies
|
||||
import NotificationService from "./service/notificationService.js";
|
||||
import NotificationUtils from "./service/notificationUtils.js";
|
||||
import NotificationService from "./service/infrastructure/notificationService.js";
|
||||
import NotificationUtils from "./service/infrastructure/notificationUtils.js";
|
||||
|
||||
// Buffer Service and dependencies
|
||||
import BufferService from "./service/bufferService.js";
|
||||
import BufferService from "./service/infrastructure/bufferService.js";
|
||||
|
||||
// Service Registry
|
||||
import ServiceRegistry from "./service/serviceRegistry.js";
|
||||
import ServiceRegistry from "./service/system/serviceRegistry.js";
|
||||
|
||||
import MongoDB from "./db/mongo/MongoDB.js";
|
||||
|
||||
// Redis Service and dependencies
|
||||
import IORedis from "ioredis";
|
||||
import RedisService from "./service/redisService.js";
|
||||
import RedisService from "./service/data/redisService.js";
|
||||
|
||||
import TranslationService from "./service/translationService.js";
|
||||
import TranslationService from "./service/system/translationService.js";
|
||||
import languageMiddleware from "./middleware/languageMiddleware.js";
|
||||
import StringService from "./service/stringService.js";
|
||||
import StringService from "./service/system/stringService.js";
|
||||
|
||||
const SERVICE_NAME = "Server";
|
||||
const SHUTDOWN_TIMEOUT = 1000;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logger from "../utils/logger.js";
|
||||
import ServiceRegistry from "../service/serviceRegistry.js";
|
||||
import StringService from "../service/stringService.js";
|
||||
import ServiceRegistry from "../service/system/serviceRegistry.js";
|
||||
import StringService from "../service/system/stringService.js";
|
||||
|
||||
const handleErrors = (error, req, res, next) => {
|
||||
console.log("ERROR", error);
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
const TOKEN_PREFIX = "Bearer ";
|
||||
const SERVICE_NAME = "allowedRoles";
|
||||
import ServiceRegistry from "../service/serviceRegistry.js";
|
||||
import StringService from "../service/stringService.js";
|
||||
import SettingsService from "../service/settingsService.js";
|
||||
import ServiceRegistry from "../service/system/serviceRegistry.js";
|
||||
import StringService from "../service/system/stringService.js";
|
||||
import SettingsService from "../service/system/settingsService.js";
|
||||
|
||||
const isAllowed = (allowedRoles) => {
|
||||
return (req, res, next) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import ServiceRegistry from "../service/serviceRegistry.js";
|
||||
import SettingsService from "../service/settingsService.js";
|
||||
import StringService from "../service/stringService.js";
|
||||
import ServiceRegistry from "../service/system/serviceRegistry.js";
|
||||
import SettingsService from "../service/system/settingsService.js";
|
||||
import StringService from "../service/system/stringService.js";
|
||||
const SERVICE_NAME = "verifyJWT";
|
||||
const TOKEN_PREFIX = "Bearer ";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import logger from "../utils/logger.js";
|
||||
import ServiceRegistry from "../service/serviceRegistry.js";
|
||||
import StringService from "../service/stringService.js";
|
||||
import ServiceRegistry from "../service/system/serviceRegistry.js";
|
||||
import StringService from "../service/system/stringService.js";
|
||||
import { ObjectId } from "mongodb";
|
||||
|
||||
const SERVICE_NAME = "verifyOwnership";
|
||||
|
||||
208
server/service/business/userService.js
Normal file
208
server/service/business/userService.js
Normal file
@@ -0,0 +1,208 @@
|
||||
const SERVICE_NAME = "userService";
|
||||
import { createAuthError, createError } from "../../utils/errorUtils.js";
|
||||
|
||||
class UserService {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor({ db, emailService, settingsService, logger, stringService, jwt }) {
|
||||
this.db = db;
|
||||
this.emailService = emailService;
|
||||
this.settingsService = settingsService;
|
||||
this.logger = logger;
|
||||
this.stringService = stringService;
|
||||
this.jwt = jwt;
|
||||
}
|
||||
|
||||
issueToken = (payload, appSettings) => {
|
||||
const tokenTTL = appSettings?.jwtTTL ?? "2h";
|
||||
const tokenSecret = appSettings?.jwtSecret;
|
||||
const payloadData = payload;
|
||||
return this.jwt.sign(payloadData, tokenSecret, { expiresIn: tokenTTL });
|
||||
};
|
||||
|
||||
registerUser = async (user, file) => {
|
||||
// Create a new user
|
||||
// If superAdmin exists, a token should be attached to all further register requests
|
||||
const superAdminExists = await this.db.checkSuperadmin();
|
||||
if (superAdminExists) {
|
||||
const invitedUser = await this.db.getInviteTokenAndDelete(user.inviteToken);
|
||||
user.role = invitedUser.role;
|
||||
user.teamId = invitedUser.teamId;
|
||||
} 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 this.db.updateAppSettings({ jwtSecret });
|
||||
}
|
||||
|
||||
const newUser = await this.db.insertUser({ ...user }, file);
|
||||
|
||||
this.logger.debug({
|
||||
message: "New user created",
|
||||
service: SERVICE_NAME,
|
||||
method: "registerUser",
|
||||
details: newUser._id,
|
||||
});
|
||||
|
||||
const userForToken = { ...newUser._doc };
|
||||
delete userForToken.profileImage;
|
||||
delete userForToken.avatarImage;
|
||||
|
||||
const appSettings = await this.settingsService.getSettings();
|
||||
|
||||
const token = this.issueToken(userForToken, appSettings);
|
||||
|
||||
try {
|
||||
const html = await this.emailService.buildEmail("welcomeEmailTemplate", {
|
||||
name: newUser.firstName,
|
||||
});
|
||||
this.emailService.sendEmail(newUser.email, "Welcome to Uptime Monitor", html).catch((error) => {
|
||||
this.logger.warn({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "registerUser",
|
||||
stack: error.stack,
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.warn({
|
||||
message: error.message,
|
||||
service: SERVICE_NAME,
|
||||
method: "registerUser",
|
||||
stack: error.stack,
|
||||
});
|
||||
}
|
||||
|
||||
return { user: newUser, token };
|
||||
};
|
||||
|
||||
loginUser = async (email, password) => {
|
||||
// Check if user exists
|
||||
const user = await this.db.getUserByEmail(email);
|
||||
// Compare password
|
||||
const match = await user.comparePassword(password);
|
||||
if (match !== true) {
|
||||
throw createAuthError(this.stringService.authIncorrectPassword);
|
||||
}
|
||||
|
||||
// Remove password from user object. Should this be abstracted to DB layer?
|
||||
const userWithoutPassword = { ...user._doc };
|
||||
delete userWithoutPassword.password;
|
||||
delete userWithoutPassword.avatarImage;
|
||||
|
||||
// Happy path, return token
|
||||
const appSettings = await this.settingsService.getSettings();
|
||||
const token = this.issueToken(userWithoutPassword, appSettings);
|
||||
// reset avatar image
|
||||
userWithoutPassword.avatarImage = user.avatarImage;
|
||||
return { user: userWithoutPassword, token };
|
||||
};
|
||||
|
||||
editUser = async (updates, file, currentUser) => {
|
||||
// Change Password check
|
||||
if (updates?.password && updates?.newPassword) {
|
||||
// Get user's email
|
||||
// Add user email to body for DB operation
|
||||
updates.email = currentUser.email;
|
||||
// Get user
|
||||
const user = await this.db.getUserByEmail(currentUser.email);
|
||||
// Compare passwords
|
||||
const match = await user.comparePassword(updates?.password);
|
||||
// If not a match, throw a 403
|
||||
// 403 instead of 401 to avoid triggering axios interceptor
|
||||
if (!match) {
|
||||
throw createError(this.stringService.authIncorrectPassword, 403);
|
||||
}
|
||||
// If a match, update the password
|
||||
updates.password = updates.newPassword;
|
||||
}
|
||||
|
||||
const updatedUser = await this.db.updateUser({ userId: currentUser?._id, user: updates, file: file });
|
||||
return updatedUser;
|
||||
};
|
||||
|
||||
checkSuperadminExists = async () => {
|
||||
const superAdminExists = await this.db.checkSuperadmin();
|
||||
return superAdminExists;
|
||||
};
|
||||
|
||||
requestRecovery = async (email) => {
|
||||
const user = await this.db.getUserByEmail(email);
|
||||
const recoveryToken = await this.db.requestRecoveryToken(email);
|
||||
const name = user.firstName;
|
||||
const { clientHost } = this.settingsService.getSettings();
|
||||
const url = `${clientHost}/set-new-password/${recoveryToken.token}`;
|
||||
|
||||
const html = await this.emailService.buildEmail("passwordResetTemplate", {
|
||||
name,
|
||||
email,
|
||||
url,
|
||||
});
|
||||
const msgId = await this.emailService.sendEmail(email, "Checkmate Password Reset", html);
|
||||
return msgId;
|
||||
};
|
||||
|
||||
validateRecovery = async (recoveryToken) => {
|
||||
await this.db.validateRecoveryToken(recoveryToken);
|
||||
};
|
||||
|
||||
resetPassword = async (password, recoveryToken) => {
|
||||
const user = await this.db.resetPassword(password, recoveryToken);
|
||||
const appSettings = await this.settingsService.getSettings();
|
||||
const token = this.issueToken(user._doc, appSettings);
|
||||
return { user, token };
|
||||
};
|
||||
|
||||
deleteUser = async (user) => {
|
||||
const email = user?.email;
|
||||
if (!email) {
|
||||
throw new Error("No email in request");
|
||||
}
|
||||
|
||||
const teamId = user?.teamId;
|
||||
const userId = user?._id;
|
||||
|
||||
if (!teamId) {
|
||||
throw new Error("No team ID in request");
|
||||
}
|
||||
|
||||
if (!userId) {
|
||||
throw new Error("No user ID in request");
|
||||
}
|
||||
|
||||
const roles = user?.role;
|
||||
if (roles.includes("demo")) {
|
||||
throw new Error("Demo user cannot be deleted");
|
||||
}
|
||||
|
||||
// 1. Find all the monitors associated with the team ID if superadmin
|
||||
const result = await this.db.getMonitorsByTeamId({
|
||||
teamId: teamId,
|
||||
});
|
||||
|
||||
if (roles.includes("superadmin")) {
|
||||
// 2. Remove all jobs, delete checks and alerts
|
||||
result?.monitors.length > 0 &&
|
||||
(await Promise.all(
|
||||
result.monitors.map(async (monitor) => {
|
||||
await this.jobQueue.deleteJob(monitor);
|
||||
})
|
||||
));
|
||||
}
|
||||
// 6. Delete the user by id
|
||||
await this.db.deleteUser(userId);
|
||||
};
|
||||
|
||||
getAllUsers = async () => {
|
||||
const users = await this.db.getAllUsers();
|
||||
return users;
|
||||
};
|
||||
|
||||
getUserById = async (roles, userId) => {
|
||||
const user = await this.db.getUserById(roles, userId);
|
||||
return user;
|
||||
};
|
||||
|
||||
editUserById = async (userId, user) => {
|
||||
await this.db.editUserById(userId, user);
|
||||
};
|
||||
}
|
||||
export default UserService;
|
||||
@@ -1,5 +1,5 @@
|
||||
import MonitorStats from "../db/models/MonitorStats.js";
|
||||
import { safelyParseFloat } from "../utils/dataUtils.js";
|
||||
import MonitorStats from "../../db/models/MonitorStats.js";
|
||||
import { safelyParseFloat } from "../../utils/dataUtils.js";
|
||||
const SERVICE_NAME = "StatusService";
|
||||
|
||||
class StatusService {
|
||||
@@ -1,5 +1,5 @@
|
||||
const SERVICE_NAME = "ServiceRegistry";
|
||||
import logger from "../utils/logger.js";
|
||||
import logger from "../../utils/logger.js";
|
||||
class ServiceRegistry {
|
||||
static SERVICE_NAME = SERVICE_NAME;
|
||||
constructor() {
|
||||
Reference in New Issue
Block a user