refactor user-service

This commit is contained in:
Alex Holliday
2026-01-16 22:41:12 +00:00
parent 9c92542653
commit b47f3710fe
11 changed files with 69 additions and 38 deletions
+4 -3
View File
@@ -192,7 +192,8 @@ class AuthController {
editUserById = async (req: Request, res: Response, next: NextFunction) => {
try {
const roles = req?.user?.role;
if (!roles.includes("superadmin")) {
if (!roles || !roles.includes("superadmin")) {
throw new AppError({ message: "Unauthorized", status: 403 });
}
@@ -201,7 +202,7 @@ class AuthController {
await editUserByIdParamValidation.validateAsync(req.params);
// If this is superadmin self edit, allow "superadmin" role
if (userId === req.user._id) {
if (userId === req.user?.id) {
await editSuperadminUserByIdBodyValidation.validateAsync(req.body);
} else {
await editUserByIdBodyValidation.validateAsync(req.body);
@@ -217,7 +218,7 @@ class AuthController {
editUserPasswordById = async (req: Request, res: Response, next: NextFunction) => {
try {
const roles = req?.user?.role;
if (!roles.includes("superadmin")) {
if (!roles || !roles.includes("superadmin")) {
throw new AppError({ message: "Unauthorized", status: 403 });
}
+1 -1
View File
@@ -69,7 +69,7 @@ class IncidentController {
try {
const resolvedIncident = await this.incidentService.resolveIncidentManually({
incidentId: req?.params?.incidentId,
userId: req?.user?._id,
userId: req?.user?.id,
teamId: req?.user?.teamId,
comment: req?.body?.comment,
});
@@ -46,7 +46,7 @@ class MaintenanceWindowController {
try {
await getMaintenanceWindowByIdParamValidation.validateAsync(req.params);
const teamId = req.user.teamId;
const teamId = req.user?.teamId;
if (!teamId) {
throw new AppError({ message: "Team ID is required", status: 400 });
}
@@ -130,7 +130,7 @@ class MaintenanceWindowController {
await editMaintenanceWindowByIdParamValidation.validateAsync(req.params);
await editMaintenanceByIdWindowBodyValidation.validateAsync(req.body);
const teamId = req.user.teamId;
const teamId = req.user?.teamId;
if (!teamId) {
throw new AppError({ message: "Team ID is required", status: 400 });
}
+4 -4
View File
@@ -164,7 +164,7 @@ class MonitorController {
try {
await createMonitorBodyValidation.validateAsync(req.body);
const userId = requireString(req?.user?._id, "User ID");
const userId = requireString(req?.user?.id, "User ID");
const teamId = requireTeamId(req?.user?.teamId);
const monitor = await this.monitorService.createMonitor(teamId, userId, req.body);
@@ -193,7 +193,7 @@ class MonitorController {
throw new AppError({ message: "File is empty", status: 400 });
}
const userId = requireString(req?.user?._id, "User ID");
const userId = requireString(req?.user?.id, "User ID");
const teamId = requireTeamId(req?.user?.teamId);
const fileData = req?.file?.buffer?.toString("utf-8");
@@ -286,9 +286,9 @@ class MonitorController {
addDemoMonitors = async (req: Request, res: Response, next: NextFunction) => {
try {
const _id = requireString(req?.user?._id, "User ID");
const id = requireString(req?.user?.id, "User ID");
const teamId = requireTeamId(req?.user?.teamId);
const demoMonitors = await this.monitorService.addDemoMonitors({ userId: _id, teamId });
const demoMonitors = await this.monitorService.addDemoMonitors({ userId: id, teamId });
return res.status(200).json({
success: true,
@@ -53,7 +53,7 @@ class NotificationController {
throw new AppError({ message: "Team ID is required", status: 400 });
}
const userId = req?.user?._id;
const userId = req?.user?.id;
if (!userId) {
throw new AppError({ message: "User ID is required", status: 400 });
}
@@ -21,11 +21,11 @@ class StatusPageController {
await createStatusPageBodyValidation.validateAsync(req.body);
await imageValidation.validateAsync(req.file);
const { _id, teamId } = req.user;
const { id, teamId } = req.user ?? {};
const statusPage = await this.db.statusPageModule.createStatusPage({
statusPageData: req.body,
image: req.file,
userId: _id,
userId: id,
teamId,
});
return res.status(200).json({
@@ -87,7 +87,7 @@ class StatusPageController {
};
getStatusPagesByTeamId = async (req: Request, res: Response, next: NextFunction) => {
try {
const teamId = req.user.teamId;
const teamId = req.user?.teamId;
const statusPages = await this.db.statusPageModule.getStatusPagesByTeamId(teamId);
return res.status(200).json({
@@ -1,3 +1,5 @@
import { NextFunction, Request, Response } from "express";
import jwt from "jsonwebtoken";
import ServiceRegistry from "../../service/system/serviceRegistry.js";
import SettingsService from "../../service/system/settingsService.js";
@@ -5,20 +7,12 @@ import StringService from "../../service/system/stringService.js";
const SERVICE_NAME = "verifyJWT";
const TOKEN_PREFIX = "Bearer ";
/**
* Verifies the JWT token
* @function
* @param {express.Request} req
* @param {express.Response} res
* @param {express.NextFunction} next
* @returns {express.Response}
*/
const verifyJWT = (req, res, next) => {
const verifyJWT = (req: Request, res: Response, next: NextFunction) => {
const stringService = ServiceRegistry.get(StringService.SERVICE_NAME);
const token = req.headers["authorization"];
// Make sure a token is provided
if (!token) {
const error = new Error(stringService.noAuthToken);
const error: any = new Error(stringService.noAuthToken);
error.status = 401;
error.service = SERVICE_NAME;
next(error);
@@ -26,7 +20,7 @@ const verifyJWT = (req, res, next) => {
}
// Make sure it is properly formatted
if (!token.startsWith(TOKEN_PREFIX)) {
const error = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token
const error: any = new Error(stringService.invalidAuthToken); // Instantiate a new Error object for improperly formatted token
error.status = 401;
error.service = SERVICE_NAME;
error.method = "verifyJWT";
@@ -37,7 +31,7 @@ const verifyJWT = (req, res, next) => {
const parsedToken = token.slice(TOKEN_PREFIX.length, token.length);
// Verify the token's authenticity
const { jwtSecret } = ServiceRegistry.get(SettingsService.SERVICE_NAME).getSettings();
jwt.verify(parsedToken, jwtSecret, (err, decoded) => {
jwt.verify(parsedToken, jwtSecret, (err: any, decoded: any) => {
if (err) {
const errorMessage = err.name === "TokenExpiredError" ? stringService.expiredAuthToken : stringService.invalidAuthToken;
err.details = { msg: errorMessage };
@@ -4,9 +4,12 @@ export interface IUsersRepository {
create(user: Partial<User>, imageFile?: Express.Multer.File | null): Promise<User>;
// fetch
findByEmail(email: string): Promise<User>;
findById(id: string): Promise<User>;
findAll(): Promise<User[]>;
// update
updateById(id: string, patch: Partial<User>, file?: Express.Multer.File | null): Promise<User>;
// delete
deleteById(id: string): Promise<User>;
// other
findSuperAdmin(): Promise<boolean>;
}
@@ -82,6 +82,20 @@ class MongoUsersRepository implements IUsersRepository {
return this.toEntity(user);
};
findById = async (id: string) => {
const user = await UserModel.findById(id).select("-password").select("-profileImage");
if (!user) {
throw new Error("User not found");
}
return this.toEntity(user);
};
findAll = async () => {
const users = await UserModel.find().select("-password").select("-profileImage");
return this.mapDocuments(users);
};
updateById = async (id: string, patch: Partial<User & { deleteProfileImage?: boolean }>, file?: Express.Multer.File | null): Promise<User> => {
const candidateUser = { ...patch };
@@ -113,6 +127,14 @@ class MongoUsersRepository implements IUsersRepository {
return this.toEntity(updatedUser);
};
deleteById = async (id: string) => {
const deletedUser = await UserModel.findByIdAndDelete(id);
if (!deletedUser) {
throw new AppError({ message: "User not found", service: SERVICE_NAME, status: 404 });
}
return this.toEntity(deletedUser);
};
findSuperAdmin = async () => {
const superAdmin = await UserModel.findOne({ role: "superadmin" });
if (superAdmin !== null) {
@@ -120,6 +142,13 @@ class MongoUsersRepository implements IUsersRepository {
}
return false;
};
private mapDocuments = (documents: UserDocument[]): User[] => {
if (!documents?.length) {
return [];
}
return documents.map((doc) => this.toEntity(doc));
};
}
export default MongoUsersRepository;
+14 -10
View File
@@ -187,7 +187,7 @@ class UserService {
};
requestRecovery = async (email: string) => {
const user = await this.db.userModule.getUserByEmail(email);
const user = await this.usersRepository.findByEmail(email);
// Delete existing tokens
await this.recoveryTokensRepository.deleteManyByEmail(email);
@@ -231,14 +231,14 @@ class UserService {
return { user: existingUser, token };
};
deleteUser = async (user: any) => {
deleteUser = async (user: User) => {
const email = user?.email;
if (!email) {
throw this.errorService.createBadRequestError("No email in request");
}
const teamId = user?.teamId;
const userId = user?._id;
const userId = user?.id;
if (!teamId) {
throw this.errorService.createBadRequestError("No team ID in request");
@@ -267,24 +267,28 @@ class UserService {
));
}
// 6. Delete the user by id
await this.db.userModule.deleteUser(userId);
await this.usersRepository.deleteById(userId);
};
getAllUsers = async () => {
const users = await this.db.userModule.getAllUsers();
return users;
return await this.usersRepository.findAll();
};
getUserById = async (roles: any, userId: any) => {
const user = await this.db.userModule.getUserById(roles, userId);
if (!roles.includes("superadmin")) {
throw new AppError({ message: "User is not a superadmin", service: SERVICE_NAME, status: 403 });
}
const user = await this.usersRepository.findById(userId);
return user;
};
editUserById = async (userId: any, user: any) => {
await this.db.userModule.editUserById(userId, user);
editUserById = async (userId: any, patch: Partial<User>) => {
await this.usersRepository.updateById(userId, patch, null);
};
setPasswordByUserId = async (userId: any, password: string) => {
const updatedUser = await this.db.userModule.updateUser({ userId: userId, user: { password: password }, file: null });
const updatedUser = await this.usersRepository.updateById(userId, { password }, null);
return updatedUser;
};
}
+2 -2
View File
@@ -1,10 +1,10 @@
import { ITokenizedUser } from "../db/models/index.ts";
import type { User } from "@/types/index.js";
declare global {
namespace Express {
interface Request {
file?: Multer.File;
user?: ITokenizedUser;
user?: User | undefined;
resource?: any;
}
}