- 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
189 lines
5.0 KiB
JavaScript
189 lines
5.0 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const multer = require('multer');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const sharp = require('sharp');
|
|
const adminAuth = require('../middleware/adminAuth');
|
|
|
|
// Ensure uploads directory exists
|
|
const uploadsDir = path.join(__dirname, '../../client/public/uploads');
|
|
if (!fs.existsSync(uploadsDir)) {
|
|
fs.mkdirSync(uploadsDir, { recursive: true });
|
|
}
|
|
|
|
// Configure multer for file upload
|
|
const storage = multer.diskStorage({
|
|
destination: (req, file, cb) => {
|
|
cb(null, uploadsDir);
|
|
},
|
|
filename: (req, file, cb) => {
|
|
// Generate unique filename
|
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
|
const ext = path.extname(file.originalname);
|
|
const name = path.basename(file.originalname, ext).replace(/[^a-z0-9]/gi, '-').toLowerCase();
|
|
cb(null, `${name}-${uniqueSuffix}${ext}`);
|
|
}
|
|
});
|
|
|
|
const fileFilter = (req, file, cb) => {
|
|
// Accept images only
|
|
if (file.mimetype.startsWith('image/')) {
|
|
cb(null, true);
|
|
} else {
|
|
cb(new Error('Only image files are allowed'), false);
|
|
}
|
|
};
|
|
|
|
const upload = multer({
|
|
storage: storage,
|
|
fileFilter: fileFilter,
|
|
limits: {
|
|
fileSize: 10 * 1024 * 1024 // 10MB limit
|
|
}
|
|
});
|
|
|
|
// @route POST /api/upload
|
|
// @desc Upload single or multiple images
|
|
// @access Private (Admin)
|
|
router.post('/', adminAuth, upload.array('images', 10), async (req, res) => {
|
|
try {
|
|
if (!req.files || req.files.length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'No files uploaded'
|
|
});
|
|
}
|
|
|
|
// Process uploaded files
|
|
const uploadedFiles = await Promise.all(
|
|
req.files.map(async (file) => {
|
|
try {
|
|
// Optimize image with sharp
|
|
const optimizedPath = path.join(uploadsDir, `optimized-${file.filename}`);
|
|
|
|
await sharp(file.path)
|
|
.resize(1920, 1080, {
|
|
fit: 'inside',
|
|
withoutEnlargement: true
|
|
})
|
|
.jpeg({ quality: 85 })
|
|
.toFile(optimizedPath);
|
|
|
|
// Replace original with optimized
|
|
fs.unlinkSync(file.path);
|
|
fs.renameSync(optimizedPath, file.path);
|
|
|
|
return {
|
|
filename: file.filename,
|
|
originalName: file.originalname,
|
|
url: `/uploads/${file.filename}`,
|
|
size: file.size,
|
|
mimeType: file.mimetype,
|
|
uploadedAt: new Date()
|
|
};
|
|
} catch (error) {
|
|
console.error('Error processing image:', error);
|
|
return {
|
|
filename: file.filename,
|
|
originalName: file.originalname,
|
|
url: `/uploads/${file.filename}`,
|
|
size: file.size,
|
|
mimeType: file.mimetype,
|
|
uploadedAt: new Date(),
|
|
error: 'Optimization failed'
|
|
};
|
|
}
|
|
})
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'Files uploaded successfully',
|
|
data: { files: uploadedFiles }
|
|
});
|
|
} catch (error) {
|
|
console.error('Upload error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: error.message || 'Error uploading files'
|
|
});
|
|
}
|
|
});
|
|
|
|
// @route GET /api/upload/list
|
|
// @desc Get list of uploaded files
|
|
// @access Private (Admin)
|
|
router.get('/list', adminAuth, async (req, res) => {
|
|
try {
|
|
const files = fs.readdirSync(uploadsDir);
|
|
|
|
const fileList = files
|
|
.filter(file => !file.startsWith('.')) // Ignore hidden files
|
|
.map(filename => {
|
|
const filePath = path.join(uploadsDir, filename);
|
|
const stats = fs.statSync(filePath);
|
|
|
|
return {
|
|
filename,
|
|
url: `/uploads/${filename}`,
|
|
size: stats.size,
|
|
uploadedAt: stats.mtime
|
|
};
|
|
})
|
|
.sort((a, b) => b.uploadedAt - a.uploadedAt); // Sort by newest first
|
|
|
|
res.json({
|
|
success: true,
|
|
data: { files: fileList, total: fileList.length }
|
|
});
|
|
} catch (error) {
|
|
console.error('List files error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error listing files'
|
|
});
|
|
}
|
|
});
|
|
|
|
// @route DELETE /api/upload/:filename
|
|
// @desc Delete an uploaded file
|
|
// @access Private (Admin)
|
|
router.delete('/:filename', adminAuth, async (req, res) => {
|
|
try {
|
|
const { filename } = req.params;
|
|
const filePath = path.join(uploadsDir, filename);
|
|
|
|
// Security check: ensure filename doesn't contain path traversal
|
|
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
message: 'Invalid filename'
|
|
});
|
|
}
|
|
|
|
if (!fs.existsSync(filePath)) {
|
|
return res.status(404).json({
|
|
success: false,
|
|
message: 'File not found'
|
|
});
|
|
}
|
|
|
|
fs.unlinkSync(filePath);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: 'File deleted successfully'
|
|
});
|
|
} catch (error) {
|
|
console.error('Delete file error:', error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: 'Error deleting file'
|
|
});
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|
|
|