фото
All checks were successful
Deploy hotel / deploy-kdo (push) Successful in 39s

This commit is contained in:
2026-07-18 12:21:03 +05:00
parent 6c4394713c
commit 22df4e3c48
4 changed files with 47 additions and 18 deletions

View File

@@ -120,7 +120,7 @@ function uploadRoomImage(req, res) {
return res.status(400).json({ error: 'Файл не загружен' });
}
let imagePath = 'uploads/rooms/' + req.file.filename;
let imagePath = 'data/room_images/' + req.file.filename;
if (req.file.mimetype !== 'image/webp') {
const inputPath = req.file.path;
@@ -131,7 +131,7 @@ function uploadRoomImage(req, res) {
.toFile(outputPath)
.then(() => {
try { fs.unlinkSync(inputPath); } catch {}
imagePath = 'uploads/rooms/' + path.basename(outputPath);
imagePath = 'data/room_images/' + path.basename(outputPath);
res.json({ path: imagePath });
})
.catch(err => {
@@ -156,7 +156,7 @@ function uploadRoomImages(req, res) {
if (index >= req.files.length) return;
const file = req.files[index];
let imagePath = 'uploads/rooms/' + file.filename;
let imagePath = 'data/room_images/' + file.filename;
function saveByPath(finalPath) {
db.get(`SELECT MAX(sort_order) as maxOrder FROM room_images WHERE room_id = ?`, [roomId], (err, row) => {
@@ -178,7 +178,7 @@ function uploadRoomImages(req, res) {
.toFile(outputPath)
.then(() => {
try { fs.unlinkSync(inputPath); } catch {}
saveByPath('uploads/rooms/' + path.basename(outputPath));
saveByPath('data/room_images/' + path.basename(outputPath));
})
.catch(() => {
saveByPath(imagePath);
@@ -293,6 +293,21 @@ function getAvailability(req, res) {
});
}
function clearLegacyImage(req, res) {
const { id } = req.params;
db.get(`SELECT image_path FROM rooms WHERE id = ?`, [id], (err, row) => {
if (err) return res.status(500).json({ error: 'Database error' });
if (!row) return res.status(404).json({ error: 'Номер не найден' });
if (!row.image_path) return res.json({ message: 'Изображение уже отсутствует' });
const filePath = path.join(__dirname, '..', '..', row.image_path);
try { if (fs.existsSync(filePath)) fs.unlinkSync(filePath); } catch {}
db.run(`UPDATE rooms SET image_path = NULL WHERE id = ?`, [id], (err) => {
if (err) return res.status(500).json({ error: 'Database error' });
res.json({ message: 'Изображение удалено' });
});
});
}
function setupRoutes(app, authenticateToken, requireAdmin, upload) {
app.get('/api/rooms', getAll);
app.get('/api/rooms/availability', getAvailability);
@@ -305,6 +320,7 @@ function setupRoutes(app, authenticateToken, requireAdmin, upload) {
app.delete('/api/admin/rooms/images/:imageId', authenticateToken, requireAdmin, deleteRoomImage);
app.put('/api/admin/rooms/images/:imageId/primary', authenticateToken, requireAdmin, setPrimaryImage);
app.put('/api/admin/rooms/images/reorder', authenticateToken, requireAdmin, reorderImages);
app.delete('/api/admin/rooms/:id/image', authenticateToken, requireAdmin, clearLegacyImage);
}
module.exports = { init, setupRoutes };

View File

@@ -1774,9 +1774,9 @@ function renderRooms(rooms) {
const primaryImg = images.find(i => i.is_primary) || images[0];
let imageSrc = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 250"%3E%3Crect fill="%23274151" width="400" height="250"/%3E%3Ctext fill="%2364748b" font-family="sans-serif" font-size="16" x="50%25" y="50%25" text-anchor="middle" dy=".3em"%3EБез фото%3C/text%3E%3C/svg%3E';
if (primaryImg && primaryImg.image_path) {
imageSrc = primaryImg.image_path.startsWith('uploads') ? '/' + primaryImg.image_path : primaryImg.image_path;
imageSrc = (primaryImg.image_path.startsWith('uploads') || primaryImg.image_path.startsWith('data')) ? '/' + primaryImg.image_path : primaryImg.image_path;
} else if (r.image_path) {
imageSrc = r.image_path.startsWith('uploads') ? '/' + r.image_path : r.image_path;
imageSrc = (r.image_path.startsWith('uploads') || r.image_path.startsWith('data')) ? '/' + r.image_path : r.image_path;
}
const statusClass = r.is_active ? 'badge-status-оплачена' : 'badge-status-отменена';
const statusText = r.is_active ? 'Активен' : 'Скрыт';
@@ -1981,14 +1981,17 @@ async function loadRoomImages(roomId) {
function renderRoomImages(images) {
const container = document.getElementById('roomImagesContainer');
if (!container) return;
container.innerHTML = images.map((img, idx) => `
container.innerHTML = images.map((img, idx) => {
const src = img.image_path.startsWith('data') ? '/' + img.image_path : '/' + img.image_path;
const hasId = img.id !== null && img.id !== undefined;
return `
<div style="position:relative; width:80px; height:60px; border-radius:6px; overflow:hidden; border:1px solid #e2e8f0;">
<img src="/${img.image_path}" style="width:100%;height:100%;object-fit:cover;">
<button type="button" onclick="deleteRoomImage(${img.id})" style="position:absolute;top:2px;right:2px;background:rgba(239,68,68,0.9);color:#fff;border:none;border-radius:50%;width:18px;height:18px;font-size:10px;cursor:pointer;line-height:1;">×</button>
<img src="${esc(src)}" style="width:100%;height:100%;object-fit:cover;">
<button type="button" onclick="${hasId ? `deleteRoomImage(${img.id})` : `clearLegacyImage(${editingRoomId})`}" style="position:absolute;top:2px;right:2px;background:rgba(239,68,68,0.9);color:#fff;border:none;border-radius:50%;width:18px;height:18px;font-size:10px;cursor:pointer;line-height:1;">×</button>
${img.is_primary ? '<span style="position:absolute;bottom:2px;left:2px;background:rgba(37,99,235,0.9);color:#fff;font-size:8px;padding:1px 4px;border-radius:3px;">Главное</span>' : ''}
${!img.is_primary && img.id ? `<button type="button" onclick="setPrimaryImage(${img.id})" style="position:absolute;bottom:2px;right:2px;background:rgba(0,0,0,0.7);color:#fff;border:none;border-radius:3px;font-size:8px;padding:1px 3px;cursor:pointer;">★</button>` : ''}
${!img.is_primary && hasId ? `<button type="button" onclick="setPrimaryImage(${img.id})" style="position:absolute;bottom:2px;right:2px;background:rgba(0,0,0,0.7);color:#fff;border:none;border-radius:3px;font-size:8px;padding:1px 3px;cursor:pointer;">★</button>` : ''}
</div>
`).join('');
`}).join('');
}
async function deleteRoomImage(imageId) {
@@ -1997,6 +2000,17 @@ async function deleteRoomImage(imageId) {
await api('/api/admin/rooms/images/' + imageId, { method: 'DELETE' });
showToast('Изображение удалено');
if (editingRoomId) loadRoomImages(editingRoomId);
else loadRooms();
} catch(err) { showToast(err.message, 'error'); }
}
async function clearLegacyImage(roomId) {
if (!confirm('Удалить изображение номера?')) return;
try {
await api('/api/admin/rooms/' + roomId + '/image', { method: 'DELETE' });
showToast('Изображение удалено');
loadRoomImages(roomId);
loadRooms();
} catch(err) { showToast(err.message, 'error'); }
}

View File

@@ -46,14 +46,14 @@ function renderRoomsPublic(rooms) {
if (primaryImg && primaryImg.image_path) {
imageSrc = primaryImg.image_path;
}
const fullImageSrc = imageSrc.startsWith('uploads') ? '/' + imageSrc : imageSrc;
const fullImageSrc = imageSrc.startsWith('uploads') || imageSrc.startsWith('data') ? '/' + imageSrc : imageSrc;
const allImages = images.length > 0 ? images : (room.image_path ? [{ image_path: room.image_path, is_primary: 1 }] : []);
let imagesHtml = '';
if (allImages.length > 1) {
const dotsHtml = allImages.map((img, i) => {
const src = img.image_path.startsWith('uploads') ? '/' + img.image_path : img.image_path;
const src = img.image_path.startsWith('uploads') || img.image_path.startsWith('data') ? '/' + img.image_path : img.image_path;
return `<span class="room-img-dot ${i === 0 ? 'active' : ''}" data-index="${i}" data-src="${escapeHtml(src)}"></span>`;
}).join('');
imagesHtml = `<div class="room-img-carousel">
@@ -149,7 +149,7 @@ function applyCarouselFrame(index, images) {
const dots = document.querySelectorAll(`#roomCarouselImg${index} ~ .room-img-dots .room-img-dot`);
if (!img || images.length === 0) return;
const state = roomCarouselState[index];
const src = images[state].image_path.startsWith('uploads') ? '/' + images[state].image_path : images[state].image_path;
const src = images[state].image_path.startsWith('uploads') || images[state].image_path.startsWith('data') ? '/' + images[state].image_path : images[state].image_path;
img.src = src;
dots.forEach((d, i) => d.classList.toggle('active', i === state));
}

View File

@@ -24,9 +24,7 @@ const JWT_SECRET = process.env.JWT_SECRET;
const MONITORING_USER = process.env.MONITORING_USER || 'monitoring';
const MONITORING_PASSWORD = process.env.MONITORING_PASSWORD || 'monitoring123';
const uploadsDir = path.join(__dirname, 'uploads');
if (!fs.existsSync(uploadsDir)) fs.mkdirSync(uploadsDir, { recursive: true });
const roomsUploadsDir = path.join(uploadsDir, 'rooms');
const roomsUploadsDir = path.join(__dirname, 'data', 'room_images');
if (!fs.existsSync(roomsUploadsDir)) fs.mkdirSync(roomsUploadsDir, { recursive: true });
const storage = multer.diskStorage({
@@ -125,6 +123,7 @@ 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((req, res, next) => {
const start = Date.now();
@@ -371,7 +370,7 @@ app.get('/api/cities/:countryCode', (req, res) => {
}
});
app.get('/uploads/rooms/:filename', (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);