номер

This commit is contained in:
2026-05-11 16:31:22 +05:00
parent fc63252327
commit 89e54538de
10 changed files with 923 additions and 159 deletions

153
server.js
View File

@@ -6,6 +6,7 @@ const sharp = require('sharp');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const client = require('prom-client');
const multer = require('multer');
require('dotenv').config();
const config = require('./config');
@@ -19,6 +20,31 @@ 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');
if (!fs.existsSync(roomsUploadsDir)) fs.mkdirSync(roomsUploadsDir, { recursive: true });
const storage = multer.diskStorage({
destination: (req, file, cb) => cb(null, roomsUploadsDir),
filename: (req, file, cb) => {
const ext = path.extname(file.originalname).toLowerCase();
const timestamp = Date.now();
cb(null, `${timestamp}${ext}`);
}
});
const upload = multer({
storage,
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: (req, file, cb) => {
const allowed = ['.jpg', '.jpeg', '.png', '.webp'];
const ext = path.extname(file.originalname).toLowerCase();
if (allowed.includes(ext)) cb(null, true);
else cb(new Error('Только изображения: jpg, jpeg, png, webp'));
}
});
if (!JWT_SECRET) {
console.error('FATAL: JWT_SECRET environment variable not set');
process.exit(1);
@@ -94,6 +120,7 @@ app.use((req, res, next) => {
next();
});
app.use(express.static(path.join(__dirname, 'public')));
app.use('/uploads', express.static(path.join(__dirname, 'uploads')));
app.use((req, res, next) => {
const start = Date.now();
@@ -224,19 +251,95 @@ db.serialize(() => {
name TEXT NOT NULL,
description TEXT,
rooms_count INTEGER DEFAULT 1,
single_beds INTEGER DEFAULT 0,
double_beds INTEGER DEFAULT 0,
has_sofa INTEGER DEFAULT 0,
has_ac INTEGER DEFAULT 0,
has_wifi INTEGER DEFAULT 0,
has_shower INTEGER DEFAULT 0,
area_sqm INTEGER DEFAULT 20,
max_guests INTEGER DEFAULT 2,
price_per_guest INTEGER NOT NULL,
furniture TEXT DEFAULT '[]',
amenities TEXT DEFAULT '[]',
floors TEXT DEFAULT '[]',
price_per_night INTEGER NOT NULL,
image_path TEXT,
extra_beds INTEGER DEFAULT 0,
extra_bed_price INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`);
setupBookingHistoryTable();
db.all("PRAGMA table_info(rooms)", [], (err, cols) => {
if (err) { setupBookingHistoryTable(); return; }
const colNames = cols.map(c => c.name);
const migrations = [
['area_sqm', 'INTEGER DEFAULT 20'],
['furniture', "TEXT DEFAULT '[]'"],
['amenities', "TEXT DEFAULT '[]'"],
['floors', "TEXT DEFAULT '[]'"],
['extra_beds', 'INTEGER DEFAULT 0'],
['extra_bed_price', 'INTEGER DEFAULT 0']
];
let pending = migrations.length;
if (pending === 0) {
migratePricePerNight();
return;
}
migrations.forEach(([colName, colDef], i) => {
if (!colNames.includes(colName)) {
db.run(`ALTER TABLE rooms ADD COLUMN ${colName} ${colDef}`, (err) => {
if (err && !err.message.includes('duplicate') && !err.message.includes('NOT NULL')) {
console.log('Room migration note:', err.message);
}
});
}
if (--pending === 0) migratePricePerNight();
});
});
}
function migratePricePerNight() {
db.all("PRAGMA table_info(rooms)", [], (err, cols) => {
if (err) { setupBookingHistoryTable(); return; }
const colNames = cols.map(c => c.name);
if (!colNames.includes('price_per_night')) {
db.run(`ALTER TABLE rooms ADD COLUMN price_per_night INTEGER DEFAULT 0`, (err) => {
if (err && !err.message.includes('duplicate') && !err.message.includes('NOT NULL')) {
console.log('Room migration note (add price_per_night):', err.message);
}
});
}
db.run(`CREATE TABLE rooms_backup AS SELECT id, type, name, description, rooms_count, area_sqm, max_guests, furniture, amenities, floors, price_per_night, image_path, extra_beds, extra_bed_price, is_active, created_at FROM rooms`, (err) => {
if (err) {
console.log('Room backup failed:', err.message);
setupBookingHistoryTable();
return;
}
db.run(`DROP TABLE rooms`, (err) => {
if (err) {
console.log('Room drop failed:', err.message);
setupBookingHistoryTable();
return;
}
db.run(`CREATE TABLE rooms (id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT NOT NULL, name TEXT NOT NULL, description TEXT, rooms_count INTEGER DEFAULT 1, area_sqm INTEGER DEFAULT 20, max_guests INTEGER DEFAULT 2, furniture TEXT DEFAULT '[]', amenities TEXT DEFAULT '[]', floors TEXT DEFAULT '[]', price_per_night INTEGER NOT NULL, image_path TEXT, extra_beds INTEGER DEFAULT 0, extra_bed_price INTEGER DEFAULT 0, is_active INTEGER DEFAULT 1, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)`, (err) => {
if (err) {
console.log('Room recreate failed:', err.message);
setupBookingHistoryTable();
return;
}
db.run(`INSERT INTO rooms (id, type, name, description, rooms_count, area_sqm, max_guests, furniture, amenities, floors, price_per_night, image_path, extra_beds, extra_bed_price, is_active, created_at) SELECT id, type, name, description, rooms_count, area_sqm, max_guests, COALESCE(furniture, '[]'), COALESCE(amenities, '[]'), COALESCE(floors, '[]'), COALESCE(price_per_night, 0) as price_per_night, image_path, COALESCE(extra_beds, 0), COALESCE(extra_bed_price, 0), is_active, created_at FROM rooms_backup`, (err) => {
if (err) console.log('Room data restore failed:', err.message);
db.run(`DROP TABLE rooms_backup`, (err) => {});
setupBookingHistoryTable();
});
});
});
});
});
}
function setupBookingHistoryTable() {
@@ -363,8 +466,27 @@ function initDefaultRooms() {
db.get("SELECT COUNT(*) as count FROM rooms", (err, row) => {
if (err) return console.error('Check rooms count error:', err);
if (row.count > 0) return;
const stmt = db.prepare(`INSERT INTO rooms (type, name, description, rooms_count, single_beds, double_beds, has_sofa, has_ac, has_wifi, has_shower, max_guests, price_per_guest, image_path, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);
config.DEFAULT_ROOMS.forEach(r => stmt.run(r.type, r.name, r.description, r.rooms_count, r.single_beds, r.double_beds, r.has_sofa, r.has_ac, r.has_wifi, r.has_shower, r.max_guests, r.price_per_guest, r.image_path, r.is_active));
const stmt = db.prepare(`INSERT INTO rooms (type, name, description, rooms_count, area_sqm, max_guests, furniture, amenities, floors, price_per_night, image_path, extra_beds, extra_bed_price, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)`);
config.DEFAULT_ROOMS.forEach(r => {
stmt.run(
r.type,
r.name,
r.description,
r.rooms_count,
r.area_sqm,
r.max_guests,
JSON.stringify(r.furniture || []),
JSON.stringify(r.amenities || []),
JSON.stringify(r.floors || []),
r.price_per_night,
r.image_path,
r.extra_beds || 0,
r.extra_bed_price || 0
);
});
stmt.finalize();
console.log('✅ Default rooms initialized');
});
@@ -402,7 +524,7 @@ authModule.setupRoutes(app, authModule.authenticateToken, authModule.requireAdmi
bookingsModule.setupRoutes(app, authModule.authenticateToken, authModule.requireAdmin);
adminBookingsModule.setupRoutes(app, authModule.authenticateToken, authModule.requireAdmin);
promocodesModule.setupRoutes(app, authModule.authenticateToken, authModule.requireAdmin);
roomsModule.setupRoutes(app);
roomsModule.setupRoutes(app, authModule.authenticateToken, authModule.requireAdmin, upload);
usersModule.setupRoutes(app, authModule.authenticateToken, authModule.requireAdmin);
settingsModule.setupRoutes(app, authModule.authenticateToken, authModule.requireAdmin);
reviewsModule.setupRoutes(app, authModule.authenticateToken, authModule.requireAdmin);
@@ -479,6 +601,15 @@ app.get('/api/cities/:countryCode', (req, res) => {
}
});
app.get('/uploads/rooms/: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('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});