- Complete Express.js API server - MongoDB integration with Mongoose - Admin authentication and authorization - Room management (CRUD operations) - Booking management system - Guest management - Payment processing (Stripe integration) - Content management (pages, blog, gallery) - Media upload and management - Integration services (Booking.com, Expedia, Opera PMS, Trip.com) - Email notifications - Comprehensive logging and error handling
213 lines
4.5 KiB
JavaScript
213 lines
4.5 KiB
JavaScript
const mongoose = require('mongoose');
|
|
const bcrypt = require('bcryptjs');
|
|
|
|
const adminSchema = new mongoose.Schema({
|
|
// Basic information
|
|
username: {
|
|
type: String,
|
|
required: true,
|
|
unique: true,
|
|
trim: true,
|
|
lowercase: true
|
|
},
|
|
email: {
|
|
type: String,
|
|
required: true,
|
|
unique: true,
|
|
trim: true,
|
|
lowercase: true
|
|
},
|
|
password: {
|
|
type: String,
|
|
required: true,
|
|
minlength: 8
|
|
},
|
|
|
|
// Profile information
|
|
firstName: {
|
|
type: String,
|
|
required: true,
|
|
trim: true
|
|
},
|
|
lastName: {
|
|
type: String,
|
|
required: true,
|
|
trim: true
|
|
},
|
|
avatar: {
|
|
type: String,
|
|
default: ''
|
|
},
|
|
|
|
// Role and permissions
|
|
role: {
|
|
type: String,
|
|
enum: ['admin', 'super-admin', 'editor', 'manager'],
|
|
default: 'admin'
|
|
},
|
|
permissions: [{
|
|
type: String,
|
|
enum: [
|
|
'manage_content', 'manage_rooms', 'manage_bookings',
|
|
'manage_users', 'manage_blog', 'manage_gallery',
|
|
'manage_settings', 'view_analytics', 'manage_admins'
|
|
]
|
|
}],
|
|
|
|
// Status
|
|
isActive: {
|
|
type: Boolean,
|
|
default: true
|
|
},
|
|
isSuperAdmin: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
|
|
// Security
|
|
lastLogin: Date,
|
|
loginAttempts: {
|
|
type: Number,
|
|
default: 0
|
|
},
|
|
lockUntil: Date,
|
|
passwordResetToken: String,
|
|
passwordResetExpires: Date,
|
|
|
|
// Session tracking
|
|
currentSessions: [{
|
|
token: String,
|
|
createdAt: Date,
|
|
expiresAt: Date,
|
|
ipAddress: String,
|
|
userAgent: String
|
|
}]
|
|
}, {
|
|
timestamps: true,
|
|
toJSON: {
|
|
virtuals: true,
|
|
transform: function(doc, ret) {
|
|
delete ret.password;
|
|
delete ret.passwordResetToken;
|
|
delete ret.currentSessions;
|
|
return ret;
|
|
}
|
|
},
|
|
toObject: {
|
|
virtuals: true,
|
|
transform: function(doc, ret) {
|
|
delete ret.password;
|
|
delete ret.passwordResetToken;
|
|
delete ret.currentSessions;
|
|
return ret;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Indexes
|
|
adminSchema.index({ username: 1 });
|
|
adminSchema.index({ email: 1 });
|
|
adminSchema.index({ role: 1 });
|
|
|
|
// Virtual for full name
|
|
adminSchema.virtual('fullName').get(function() {
|
|
return `${this.firstName} ${this.lastName}`;
|
|
});
|
|
|
|
// Virtual for account locked status
|
|
adminSchema.virtual('isLocked').get(function() {
|
|
return !!(this.lockUntil && this.lockUntil > Date.now());
|
|
});
|
|
|
|
// Pre-save middleware to hash password
|
|
adminSchema.pre('save', async function(next) {
|
|
if (!this.isModified('password')) {
|
|
return next();
|
|
}
|
|
|
|
try {
|
|
const salt = await bcrypt.genSalt(10);
|
|
this.password = await bcrypt.hash(this.password, salt);
|
|
next();
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// Method to compare password
|
|
adminSchema.methods.comparePassword = async function(candidatePassword) {
|
|
try {
|
|
return await bcrypt.compare(candidatePassword, this.password);
|
|
} catch (error) {
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
// Method to increment login attempts
|
|
adminSchema.methods.incLoginAttempts = function() {
|
|
// If we have a previous lock that has expired, restart at 1
|
|
if (this.lockUntil && this.lockUntil < Date.now()) {
|
|
return this.updateOne({
|
|
$set: { loginAttempts: 1 },
|
|
$unset: { lockUntil: 1 }
|
|
});
|
|
}
|
|
|
|
// Otherwise increment
|
|
const updates = { $inc: { loginAttempts: 1 } };
|
|
|
|
// Lock the account after 5 attempts for 2 hours
|
|
const needsLock = this.loginAttempts + 1 >= 5 && !this.isLocked;
|
|
if (needsLock) {
|
|
updates.$set = { lockUntil: Date.now() + 2 * 60 * 60 * 1000 };
|
|
}
|
|
|
|
return this.updateOne(updates);
|
|
};
|
|
|
|
// Method to reset login attempts
|
|
adminSchema.methods.resetLoginAttempts = function() {
|
|
return this.updateOne({
|
|
$set: { loginAttempts: 0 },
|
|
$unset: { lockUntil: 1 }
|
|
});
|
|
};
|
|
|
|
// Static method to find by credentials
|
|
adminSchema.statics.findByCredentials = async function(username, password) {
|
|
const admin = await this.findOne({
|
|
$or: [{ username }, { email: username }],
|
|
isActive: true
|
|
});
|
|
|
|
if (!admin) {
|
|
throw new Error('Invalid credentials');
|
|
}
|
|
|
|
// Check if account is locked
|
|
if (admin.isLocked) {
|
|
throw new Error('Account is temporarily locked. Please try again later.');
|
|
}
|
|
|
|
const isMatch = await admin.comparePassword(password);
|
|
|
|
if (!isMatch) {
|
|
await admin.incLoginAttempts();
|
|
throw new Error('Invalid credentials');
|
|
}
|
|
|
|
// Reset login attempts on successful login
|
|
if (admin.loginAttempts > 0) {
|
|
await admin.resetLoginAttempts();
|
|
}
|
|
|
|
// Update last login
|
|
admin.lastLogin = new Date();
|
|
await admin.save();
|
|
|
|
return admin;
|
|
};
|
|
|
|
module.exports = mongoose.model('Admin', adminSchema);
|
|
|