номер
This commit is contained in:
153
server.js
153
server.js
@@ -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'));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user