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:
379
routes/admin.js
Normal file
379
routes/admin.js
Normal 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;
|
||||
Reference in New Issue
Block a user