This commit is contained in:
@@ -187,6 +187,10 @@ function uploadActivityImage(req, res) {
|
||||
res.json({ message: 'Image uploaded', path: imagePath, filename });
|
||||
}
|
||||
|
||||
function getAllActivities(req, res) {
|
||||
getAdminActivities(req, res);
|
||||
}
|
||||
|
||||
function setupRoutes(app, authenticateToken, requireAdmin, upload) {
|
||||
app.get('/api/activities', getPublicActivities);
|
||||
|
||||
@@ -198,8 +202,4 @@ function setupRoutes(app, authenticateToken, requireAdmin, upload) {
|
||||
app.post('/api/admin/activities/:id/image', authenticateToken, upload.single('image'), uploadActivityImage);
|
||||
}
|
||||
|
||||
function getAllActivities(req, res) {
|
||||
getAdminActivities(req, res);
|
||||
}
|
||||
|
||||
module.exports = { init, setupRoutes, getUploadsDir };
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const config = require('../../config');
|
||||
const { validatePromocode } = require('../bookings');
|
||||
|
||||
const promocodeRateLimit = new Map();
|
||||
const PROMOCODE_WINDOW = 60 * 1000;
|
||||
@@ -37,22 +38,6 @@ function init(database) {
|
||||
db = database;
|
||||
}
|
||||
|
||||
function validatePromocode(promocode, callback) {
|
||||
if (!promocode) return callback(null, null);
|
||||
const now = new Date().toISOString();
|
||||
db.get(`SELECT * FROM promocodes WHERE code = ? AND is_active = 1`, [promocode], (err, row) => {
|
||||
if (err || !row) return callback(null, null);
|
||||
if (row.valid_from && row.valid_from > now) return callback(null, null);
|
||||
if (row.valid_to && row.valid_to < now) return callback(null, null);
|
||||
if (row.valid_days) {
|
||||
const createdDate = new Date(row.created_at);
|
||||
const expireDate = new Date(createdDate.getTime() + row.valid_days * 24 * 60 * 60 * 1000);
|
||||
if (expireDate < new Date()) return callback(null, null);
|
||||
}
|
||||
callback(null, row);
|
||||
});
|
||||
}
|
||||
|
||||
function getBookingsForAdmin(req, res) {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 20;
|
||||
@@ -152,7 +137,8 @@ function updateBookingRoom(req, res) {
|
||||
|
||||
const oldValue = booking.room_name ? booking.room_type + ' — ' + booking.room_name : booking.room_type || 'Не указан';
|
||||
const newValue = room.type + ' — ' + room.name;
|
||||
const basePrice = config.calculateBasePrice(room.type, booking.checkin_date, booking.checkout_date);
|
||||
const totalGuests = (booking.adults || 0) + (booking.children || 0);
|
||||
const basePrice = config.calculateBasePrice(room.type, booking.checkin_date, booking.checkout_date) * Math.max(1, totalGuests);
|
||||
const discountAmount = Math.round(basePrice * (booking.discount_percent || 0) / 100);
|
||||
const totalPrice = basePrice - discountAmount;
|
||||
|
||||
@@ -375,7 +361,7 @@ function exportCSV(req, res) {
|
||||
params.push(from);
|
||||
}
|
||||
if (to) {
|
||||
whereClause += ' AND b.checkout_date <= ?';
|
||||
whereClause += ' AND b.checkin_date <= ?';
|
||||
params.push(to);
|
||||
}
|
||||
|
||||
@@ -467,7 +453,7 @@ function getCalendar(req, res) {
|
||||
}
|
||||
|
||||
function setupRoutes(app, authenticateToken, requireAdmin) {
|
||||
app.get('/api/admin/bookings', authenticateToken, getBookingsForAdmin);
|
||||
app.get('/api/admin/bookings', authenticateToken, requireAdmin, getBookingsForAdmin);
|
||||
app.patch('/api/admin/bookings/:id', authenticateToken, requireAdmin, updateBookingStatus);
|
||||
app.patch('/api/admin/bookings/:id/room', authenticateToken, requireAdmin, updateBookingRoom);
|
||||
app.patch('/api/admin/bookings/:id/comment', authenticateToken, requireAdmin, updateBookingComment);
|
||||
|
||||
@@ -31,15 +31,19 @@ function calculateNightPrices(roomType, checkin, checkout, callback) {
|
||||
|
||||
let total = 0;
|
||||
let processed = 0;
|
||||
let hasError = false;
|
||||
let finished = false;
|
||||
|
||||
dates.forEach(date => {
|
||||
getEffectivePrice(roomType, date, (err, price) => {
|
||||
if (hasError) return;
|
||||
if (err) { hasError = true; return callback(err, null); }
|
||||
if (finished) return;
|
||||
if (err) {
|
||||
finished = true;
|
||||
return callback(err, null);
|
||||
}
|
||||
total += price;
|
||||
processed++;
|
||||
if (processed === dates.length) {
|
||||
finished = true;
|
||||
callback(null, total);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -149,7 +149,7 @@ function uploadRoomImages(req, res) {
|
||||
return res.status(400).json({ error: 'Файлы не загружены' });
|
||||
}
|
||||
|
||||
let processed = 0;
|
||||
let completed = 0;
|
||||
const results = [];
|
||||
|
||||
function processFile(index) {
|
||||
@@ -165,7 +165,12 @@ function uploadRoomImages(req, res) {
|
||||
[roomId, finalPath, sortOrder], function(insErr) {
|
||||
if (insErr) { console.error('Room image insert error:', insErr); }
|
||||
else results.push({ id: this.lastID, image_path: finalPath, sort_order: sortOrder });
|
||||
completed++;
|
||||
if (completed === req.files.length) {
|
||||
res.json({ message: 'Изображения загружены', images: results });
|
||||
} else {
|
||||
processFile(index + 1);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -181,7 +186,12 @@ function uploadRoomImages(req, res) {
|
||||
saveByPath('data/room_images/' + path.basename(outputPath));
|
||||
})
|
||||
.catch(() => {
|
||||
saveByPath(imagePath);
|
||||
completed++;
|
||||
if (completed === req.files.length) {
|
||||
res.json({ message: 'Изображения загружены', images: results });
|
||||
} else {
|
||||
processFile(index + 1);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
saveByPath(imagePath);
|
||||
@@ -189,20 +199,6 @@ function uploadRoomImages(req, res) {
|
||||
}
|
||||
|
||||
processFile(0);
|
||||
|
||||
const checkDone = setInterval(() => {
|
||||
if (results.length + (processed > req.files.length ? 0 : 0) >= req.files.length) {
|
||||
clearInterval(checkDone);
|
||||
if (results.length === 0) {
|
||||
// Still processing sharp conversions, wait a bit more
|
||||
setTimeout(() => {
|
||||
res.json({ message: 'Изображения загружены', images: results });
|
||||
}, 1000);
|
||||
} else {
|
||||
res.json({ message: 'Изображения загружены', images: results });
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function deleteRoomImage(req, res) {
|
||||
|
||||
@@ -142,8 +142,7 @@ function setupRoutes(app, authenticateToken, requireAdmin) {
|
||||
|
||||
res.json({
|
||||
message: `${envVarName} перегенерирован. НЕОБХОДИМО перезапустить сервер для применения нового ключа.${jwtWarning}`,
|
||||
key: secret,
|
||||
warning: 'Сохраните этот ключ. Он будет показан только один раз. После перезапуска сервера старый ключ перестанет работать.'
|
||||
warning: 'Ключ сохранён в .env. После перезапуска сервера старый ключ перестанет работать.'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
"sharp": "^0.34.5",
|
||||
"sqlite3": "^6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "nodemon server.js"
|
||||
|
||||
@@ -66,8 +66,10 @@ if (statsSection) counterObserver.observe(statsSection);
|
||||
|
||||
// Set min date for checkin to today
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
document.querySelector('[name="checkin"]').min = today;
|
||||
document.querySelector('[name="checkout"]').min = today;
|
||||
const checkinInput = document.querySelector('[name="checkin"]');
|
||||
const checkoutInput = document.querySelector('[name="checkout"]');
|
||||
if (checkinInput) checkinInput.min = today;
|
||||
if (checkoutInput) checkoutInput.min = today;
|
||||
|
||||
// Booking modal - set room name
|
||||
document.querySelectorAll('.btn-book').forEach(btn => {
|
||||
|
||||
44
server.js
44
server.js
@@ -179,9 +179,10 @@ if (!API_KEY) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
app.use(express.json({ limit: '10kb' }));
|
||||
app.use(express.json({ limit: '1mb' }));
|
||||
const CORS_ORIGIN = process.env.CORS_ORIGIN || '*';
|
||||
app.use((req, res, next) => {
|
||||
res.header('Access-Control-Allow-Origin', '*');
|
||||
res.header('Access-Control-Allow-Origin', CORS_ORIGIN);
|
||||
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS');
|
||||
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
if (req.method === 'OPTIONS') return res.sendStatus(200);
|
||||
@@ -189,7 +190,10 @@ app.use((req, res, next) => {
|
||||
});
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
|
||||
app.use('/data', express.static(path.join(__dirname, 'data')));
|
||||
app.use('/data/room_images', express.static(roomsUploadsDir));
|
||||
app.use('/data/activity_images', express.static(activityUploadsDir));
|
||||
app.use('/data/hero_images', express.static(heroImagesDir));
|
||||
app.use('/data/hero_videos', express.static(heroVideosDir));
|
||||
|
||||
app.use((req, res, next) => {
|
||||
const start = Date.now();
|
||||
@@ -444,41 +448,7 @@ app.get('/api/cities/:countryCode', (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/data/room_images/:filename', (req, res) => {
|
||||
const filePath = path.join(roomsUploadsDir, req.params.filename);
|
||||
if (fs.existsSync(filePath)) {
|
||||
res.sendFile(filePath);
|
||||
} else {
|
||||
res.status(404).json({ error: 'File not found' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/data/activity_images/:filename', (req, res) => {
|
||||
const filePath = path.join(activityUploadsDir, req.params.filename);
|
||||
if (fs.existsSync(filePath)) {
|
||||
res.sendFile(filePath);
|
||||
} else {
|
||||
res.status(404).json({ error: 'File not found' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/data/hero_images/:filename', (req, res) => {
|
||||
const filePath = path.join(heroImagesDir, req.params.filename);
|
||||
if (fs.existsSync(filePath)) {
|
||||
res.sendFile(filePath);
|
||||
} else {
|
||||
res.status(404).json({ error: 'File not found' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/data/hero_videos/:filename', (req, res) => {
|
||||
const filePath = path.join(heroVideosDir, req.params.filename);
|
||||
if (fs.existsSync(filePath)) {
|
||||
res.sendFile(filePath);
|
||||
} else {
|
||||
res.status(404).json({ error: 'File not found' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
|
||||
Reference in New Issue
Block a user