Spaces:
Running
Running
| 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; | |