Source

models/User.model.js

import { logger } from "@config/logger";
import mongoose, { Schema } from "mongoose";
import { PasswordUtils } from "../utils/password.utils.js";
/**
 * Mongoose schema describing application users.
 * @category Models
 */
const UserSchema = new Schema({
    email: {
        type: String,
        required: true,
        unique: true,
        lowercase: true,
        trim: true,
        match: [/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/, "Please enter a valid email"],
    },
    name: {
        type: String,
        required: true,
        trim: true,
        minlength: 2,
        maxlength: 50,
    },
    avatar: {
        type: String,
        default: null,
    },
    passwordHash: {
        type: String,
        required: true,
        minlength: 60, // bcrypt hash length
    },
    role: {
        type: String,
        required: true,
        enum: ["admin", "sme"],
        default: "sme",
    },
}, {
    timestamps: true,
    toJSON: {
        transform: function (doc, ret) {
            return ret;
        },
    },
});
// Index for performance
UserSchema.index({ role: 1 });
/**
 * Compare a candidate password to the stored bcrypt hash.
 * @category Models
 * @param {string} candidatePassword Plain text password to validate.
 * @returns {Promise<boolean>} Resolves true when the password matches.
 */
UserSchema.methods.comparePassword = async function (candidatePassword) {
    try {
        return await PasswordUtils.comparePassword(candidatePassword, this.passwordHash);
    }
    catch (error) {
        logger.error({
            error,
            message: "Password comparison failed",
        });
        throw new Error("Password comparison failed");
    }
};
/**
 * Hash a password using the configured password utility.
 * @category Models
 * @param {string} password Plain text password to hash.
 * @returns {Promise<string>} Bcrypt hash.
 */
UserSchema.statics.hashPassword = async function (password) {
    return await PasswordUtils.hashPassword(password);
};
/**
 * Pre-save hook responsible for password hashing flow.
 * @category Models
 */
UserSchema.pre("save", function (next) {
    // Only hash the password if it has been modified (or is new)
    if (!this.isModified("passwordHash")) {
        return next();
    }
    // If passwordHash is being set directly (not through password field), skip hashing
    if (this.isNew && this.passwordHash) {
        return next();
    }
    next();
});
/**
 * User model exposing persistence helpers for platform accounts.
 * @category Models
 */
export const User = mongoose.model("User", UserSchema);