фикс
All checks were successful
Deploy hotel / deploy-kdo (push) Successful in 34s

This commit is contained in:
2026-07-19 00:42:39 +05:00
parent 7c7cf1b1aa
commit 38117f003d
8 changed files with 44 additions and 84 deletions

View File

@@ -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 };

View File

@@ -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);

View File

@@ -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);
}
});

View File

@@ -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 });
processFile(index + 1);
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) {

View File

@@ -142,8 +142,7 @@ function setupRoutes(app, authenticateToken, requireAdmin) {
res.json({
message: `${envVarName} перегенерирован. НЕОБХОДИМО перезапустить сервер для применения нового ключа.${jwtWarning}`,
key: secret,
warning: 'Сохраните этот ключ. Он будет показан только один раз. После перезапуска сервера старый ключ перестанет работать.'
warning: 'Ключ сохранён в .env. После перезапуска сервера старый ключ перестанет работать.'
});
});

View File

@@ -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"

View File

@@ -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 => {

View File

@@ -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'));