Initial commit: CMS backend for Old Vine Hotel

- 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
This commit is contained in:
Talal Sharabi
2026-01-06 12:21:56 +04:00
commit a3308a26e2
48 changed files with 15294 additions and 0 deletions

212
models/Admin.js Normal file
View File

@@ -0,0 +1,212 @@
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);