const mongoose = require('mongoose'); const bcrypt = require('bcryptjs'); const addressSchema = new mongoose.Schema({ governorate: { type: String, required: true }, city: { type: String, required: true }, district: { type: String, required: true }, street: { type: String, required: true }, details: { type: String }, }); const userSchema = new mongoose.Schema( { name: { type: String, required: [true, 'Please provide your name'], trim: true, }, email: { type: String, required: [true, 'Please provide your email'], unique: true, lowercase: true, }, phone: { type: String, required: [true, 'Please provide your phone number'], unique: true, }, password: { type: String, required: [true, 'Please provide a password'], minlength: 6, select: false, // hide password by default }, role: { type: String, enum: ['customer', 'admin', 'employee', 'vendor'], default: 'customer', }, permissions: { type: [String], default: [], // Only relevant for employees - will contain permission strings like 'manage_products', 'view_orders', etc. }, provider: { type: mongoose.Schema.Types.ObjectId, ref: 'Provider', // Only relevant for users with role 'vendor' - links to their Provider document }, isActive: { type: Boolean, default: true, }, addresses: [addressSchema], passwordResetToken: { type: String, select: false, // Don't include in queries by default }, passwordResetExpires: { type: Date, select: false, }, passwordChangedAt: Date, notificationPreferences: { orderUpdates: { email: { type: Boolean, default: true }, app: { type: Boolean, default: true }, }, newsletters: { email: { type: Boolean, default: true }, app: { type: Boolean, default: true }, }, promotions: { email: { type: Boolean, default: true }, app: { type: Boolean, default: true }, }, productUpdates: { email: { type: Boolean, default: true }, app: { type: Boolean, default: true }, }, vendorOrderVisibility: { email: { type: Boolean, default: true }, app: { type: Boolean, default: true }, disabledAt: { type: Date }, blackoutPeriods: [ { start: { type: Date }, end: { type: Date }, }, ], }, }, recentSearches: { type: [String], default: [], }, }, { timestamps: true, // Adds createdAt and updatedAt fields automatically }, ); // Encrypt password before saving user userSchema.pre('save', async function (next) { // Only run this function if password was actually modified if (!this.isModified('password')) return next(); // Hash the password with cost of 12 this.password = await bcrypt.hash(this.password, 12); // Update passwordChangedAt field if (!this.isNew) { this.passwordChangedAt = Date.now() - 1000; } next(); }); // Instance method to check password validity userSchema.methods.correctPassword = async function ( candidatePassword, userPassword, ) { return await bcrypt.compare(candidatePassword, userPassword); }; // Instance method to create password reset token userSchema.methods.createPasswordResetToken = function () { const crypto = require('crypto'); // Generate token const resetToken = crypto.randomBytes(32).toString('hex'); // Hash token and save to database this.passwordResetToken = crypto .createHash('sha256') .update(resetToken) .digest('hex'); // Set expiration to 10 minutes from now this.passwordResetExpires = Date.now() + 10 * 60 * 1000; // 10 minutes // Return unhashed token to send to user return resetToken; }; // Instance method to check if password was changed after JWT was issued userSchema.methods.changedPasswordAfter = function (JWTTimestamp) { if (this.passwordChangedAt) { const changedTimestamp = parseInt( this.passwordChangedAt.getTime() / 1000, 10, ); return JWTTimestamp < changedTimestamp; } return false; }; userSchema.index({ role: 1 }); const User = mongoose.model('User', userSchema); module.exports = User;