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

379
routes/admin.js Normal file
View File

@@ -0,0 +1,379 @@
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const Admin = require('../models/Admin');
const adminAuth = require('../middleware/adminAuth');
const { body, validationResult } = require('express-validator');
// @route POST /api/admin/login
// @desc Admin login
// @access Public
router.post('/login', [
body('username').notEmpty().trim().withMessage('Username or email is required'),
body('password').notEmpty().withMessage('Password is required')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { username, password } = req.body;
// Find admin by credentials
const admin = await Admin.findByCredentials(username, password);
// Generate JWT token
const token = jwt.sign(
{
id: admin._id,
email: admin.email,
role: admin.role,
isAdmin: true
},
process.env.JWT_SECRET || 'your-secret-key',
{ expiresIn: '7d' }
);
res.json({
success: true,
message: 'Login successful',
data: {
token,
admin: {
id: admin._id,
username: admin.username,
email: admin.email,
firstName: admin.firstName,
lastName: admin.lastName,
fullName: admin.fullName,
avatar: admin.avatar,
role: admin.role,
permissions: admin.permissions
}
}
});
} catch (error) {
console.error('Admin login error:', error);
res.status(401).json({
success: false,
message: error.message || 'Invalid credentials'
});
}
});
// @route POST /api/admin/register
// @desc Register new admin (super-admin only)
// @access Private (Super Admin)
router.post('/register', adminAuth, [
body('username').notEmpty().trim().toLowerCase().withMessage('Username is required'),
body('email').isEmail().normalizeEmail().withMessage('Valid email is required'),
body('password').isLength({ min: 8 }).withMessage('Password must be at least 8 characters'),
body('firstName').notEmpty().trim().withMessage('First name is required'),
body('lastName').notEmpty().trim().withMessage('Last name is required'),
body('role').optional().isIn(['admin', 'editor', 'manager']).withMessage('Invalid role')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
// Check if requester is super admin
const requester = await Admin.findById(req.admin.id);
if (!requester || !requester.isSuperAdmin) {
return res.status(403).json({
success: false,
message: 'Only super admins can create new admin accounts'
});
}
const { username, email, password, firstName, lastName, role, permissions } = req.body;
// Check if admin already exists
const existingAdmin = await Admin.findOne({
$or: [{ username }, { email }]
});
if (existingAdmin) {
return res.status(400).json({
success: false,
message: 'Admin with this username or email already exists'
});
}
// Create new admin
const newAdmin = new Admin({
username,
email,
password,
firstName,
lastName,
role: role || 'admin',
permissions: permissions || ['manage_content', 'manage_rooms', 'manage_bookings']
});
await newAdmin.save();
res.status(201).json({
success: true,
message: 'Admin account created successfully',
data: {
admin: {
id: newAdmin._id,
username: newAdmin.username,
email: newAdmin.email,
fullName: newAdmin.fullName,
role: newAdmin.role,
permissions: newAdmin.permissions
}
}
});
} catch (error) {
console.error('Admin registration error:', error);
res.status(500).json({
success: false,
message: 'Error creating admin account'
});
}
});
// @route GET /api/admin/me
// @desc Get current admin profile
// @access Private (Admin)
router.get('/me', adminAuth, async (req, res) => {
try {
const admin = await Admin.findById(req.admin.id);
if (!admin) {
return res.status(404).json({
success: false,
message: 'Admin not found'
});
}
res.json({
success: true,
data: { admin }
});
} catch (error) {
console.error('Get admin profile error:', error);
res.status(500).json({
success: false,
message: 'Error fetching admin profile'
});
}
});
// @route PUT /api/admin/me
// @desc Update current admin profile
// @access Private (Admin)
router.put('/me', adminAuth, [
body('firstName').optional().notEmpty().trim(),
body('lastName').optional().notEmpty().trim(),
body('email').optional().isEmail().normalizeEmail(),
body('avatar').optional().isURL()
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { firstName, lastName, email, avatar } = req.body;
const updateFields = {};
if (firstName) updateFields.firstName = firstName;
if (lastName) updateFields.lastName = lastName;
if (email) updateFields.email = email;
if (avatar) updateFields.avatar = avatar;
const admin = await Admin.findByIdAndUpdate(
req.admin.id,
{ $set: updateFields },
{ new: true, runValidators: true }
);
res.json({
success: true,
message: 'Profile updated successfully',
data: { admin }
});
} catch (error) {
console.error('Update admin profile error:', error);
res.status(500).json({
success: false,
message: 'Error updating profile'
});
}
});
// @route PUT /api/admin/change-password
// @desc Change admin password
// @access Private (Admin)
router.put('/change-password', adminAuth, [
body('currentPassword').notEmpty().withMessage('Current password is required'),
body('newPassword').isLength({ min: 8 }).withMessage('New password must be at least 8 characters')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { currentPassword, newPassword } = req.body;
const admin = await Admin.findById(req.admin.id);
if (!admin) {
return res.status(404).json({
success: false,
message: 'Admin not found'
});
}
// Verify current password
const isMatch = await admin.comparePassword(currentPassword);
if (!isMatch) {
return res.status(401).json({
success: false,
message: 'Current password is incorrect'
});
}
// Update password
admin.password = newPassword;
await admin.save();
res.json({
success: true,
message: 'Password changed successfully'
});
} catch (error) {
console.error('Change password error:', error);
res.status(500).json({
success: false,
message: 'Error changing password'
});
}
});
// @route GET /api/admin/list
// @desc Get all admins (super-admin only)
// @access Private (Super Admin)
router.get('/list', adminAuth, async (req, res) => {
try {
const requester = await Admin.findById(req.admin.id);
if (!requester || !requester.isSuperAdmin) {
return res.status(403).json({
success: false,
message: 'Only super admins can view admin list'
});
}
const admins = await Admin.find().sort({ createdAt: -1 });
res.json({
success: true,
data: { admins }
});
} catch (error) {
console.error('Get admin list error:', error);
res.status(500).json({
success: false,
message: 'Error fetching admin list'
});
}
});
// @route GET /api/admin/stats
// @desc Get dashboard statistics
// @access Private (Admin)
router.get('/stats', adminAuth, async (req, res) => {
try {
const Booking = require('../models/Booking');
const Room = require('../models/Room');
const Guest = require('../models/Guest');
const BlogPost = require('../models/BlogPost');
const [
totalBookings,
activeBookings,
totalRooms,
availableRooms,
totalGuests,
totalBlogPosts
] = await Promise.all([
Booking.countDocuments(),
Booking.countDocuments({ status: { $in: ['Confirmed', 'Checked In'] } }),
Room.countDocuments(),
Room.countDocuments({ status: 'Available', isActive: true }),
Guest.countDocuments(),
BlogPost.countDocuments({ status: 'published' })
]);
// Get revenue for current month
const startOfMonth = new Date(new Date().getFullYear(), new Date().getMonth(), 1);
const revenueData = await Booking.aggregate([
{
$match: {
status: { $in: ['Confirmed', 'Checked In', 'Checked Out'] },
createdAt: { $gte: startOfMonth }
}
},
{
$group: {
_id: null,
totalRevenue: { $sum: '$totalAmount' }
}
}
]);
const monthlyRevenue = revenueData.length > 0 ? revenueData[0].totalRevenue : 0;
res.json({
success: true,
data: {
stats: {
bookings: {
total: totalBookings,
active: activeBookings
},
rooms: {
total: totalRooms,
available: availableRooms
},
guests: totalGuests,
blogPosts: totalBlogPosts,
revenue: {
monthly: monthlyRevenue
}
}
}
});
} catch (error) {
console.error('Get stats error:', error);
res.status(500).json({
success: false,
message: 'Error fetching statistics'
});
}
});
// Health check
router.get('/health', (req, res) => {
res.json({ success: true, service: 'admin', status: 'ok' });
});
module.exports = router;