Source

services/admin.service.js

import { logger } from "../config/logger.js";
import { User } from "../models/User.model.js";
import { PasswordUtils } from "../utils/password.utils.js";
/**
 * Service responsible for privileged user management operations.
 * @category Services
 */
export class AdminService {
    /**
     * Create a new user using the provided payload.
     * @param {CreateUserRequest} userData User creation payload from gRPC contract.
     * @returns {Promise<TUser>} Newly created gRPC user representation.
     */
    async createUser(userData) {
        try {
            const { email, name, password, role } = userData;
            // Check if user already exists
            const existingUser = await User.findOne({ email: email.toLowerCase() });
            if (existingUser) {
                throw new Error("User with this email already exists");
            }
            // Hash password
            const passwordHash = await PasswordUtils.hashPassword(password);
            // Create user
            const user = new User({
                email: email.toLowerCase(),
                name,
                passwordHash,
                role,
            });
            await user.save();
            logger.info(`User created: ${email} with role: ${role}`);
            return {
                id: user._id.toString(),
                email: user.email,
                name: user.name,
                role,
            };
        }
        catch (error) {
            logger.error({
                error,
                message: "User creation failed",
            });
            throw error;
        }
    }
    /**
     * Reset a user's password by email.
     * @param {IResetPasswordDto} resetData DTO containing the email and new password.
     * @returns {Promise<void>} Resolves when the password is updated.
     */
    async resetPassword(resetData) {
        try {
            const { email, newPassword } = resetData;
            const user = await User.findOne({ email: email.toLowerCase() });
            if (!user) {
                throw new Error("User not found");
            }
            // Hash new password
            const passwordHash = await PasswordUtils.hashPassword(newPassword);
            // Update password
            user.passwordHash = passwordHash;
            await user.save();
            logger.info(`Password reset for user: ${email}`);
        }
        catch (error) {
            logger.error({
                error,
                message: "Password reset failed",
            });
            throw error;
        }
    }
    /**
     * Delete a user ensuring admins cannot remove themselves.
     * @param {string} email Target user's email.
     * @param {string} currentUserEmail Authenticated admin's email.
     * @returns {Promise<void>} Resolves when deletion completes.
     */
    async deleteUser(email, currentUserEmail) {
        try {
            logger.info({ email, currentUserEmail, message: "Deleting user by email" });
            // Prevent admin from deleting themselves
            if (email.toLowerCase() === currentUserEmail.toLowerCase()) {
                throw new Error("Admins cannot delete their own account");
            }
            const user = await User.findOne({ email: email.toLowerCase() });
            if (!user) {
                throw new Error("User not found");
            }
            // Delete avatar from GCS if exists
            if (user.avatar) {
                try {
                    const { gcsService } = await import("./gcs.service.js");
                    await gcsService.deleteAvatar(user.avatar);
                }
                catch (error) {
                    logger.warn({ error }, "Failed to delete avatar from GCS:");
                }
            }
            // Delete user
            await User.findByIdAndDelete(user._id);
            logger.info(`User deleted: ${email}`);
        }
        catch (error) {
            logger.error({
                error,
                message: "User deletion by email failed",
            });
            throw error;
        }
    }
    /**
     * Retrieve paginated SMEs/admins excluding the current user.
     * @param {string} currentUserId Authenticated admin ID to exclude.
     * @param {number} page Page number (default: 1).
     * @param {number} limit Page size (default: 10).
     * @param {string} [search] Optional search term.
     * @returns {Promise<IPaginatedResponse<ISmeResponse>>} Paginated response payload.
     */
    async getAllSmes(currentUserId, page = 1, limit = 10, search) {
        try {
            const query = {
                _id: { $ne: currentUserId },
            };
            if (search) {
                query.$or = [
                    { email: { $regex: search, $options: "i" } },
                    { name: { $regex: search, $options: "i" } },
                ];
            }
            const skip = (page - 1) * limit;
            const total = await User.countDocuments(query);
            const users = await User.find(query)
                .select("-passwordHash")
                .sort({ createdAt: -1 }) // Sort by newest first
                .skip(skip)
                .limit(limit)
                .lean();
            const data = users.map((user) => ({
                id: user._id.toString(),
                email: user.email,
                name: user.name,
                role: user.role,
                avatar: user.avatar || null,
                createdAt: user.createdAt,
                updatedAt: user.updatedAt,
            }));
            logger.info({
                page,
                limit,
                total,
                search,
                message: "Retrieved admins and SMEs with pagination",
            });
            return {
                data,
                pagination: {
                    page,
                    limit,
                    total,
                    totalPages: Math.ceil(total / limit),
                },
            };
        }
        catch (error) {
            logger.error({
                error,
                message: "Failed to retrieve admins and SMEs",
            });
            throw error;
        }
    }
}