This commit is contained in:
@@ -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 };
|
||||
|
||||
@@ -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'); }
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user