293 lines
11 KiB
JavaScript
293 lines
11 KiB
JavaScript
const path = require('path');
|
|
const fs = require('fs');
|
|
const crypto = require('crypto');
|
|
const dotenv = require('dotenv');
|
|
|
|
const envPath = path.join(__dirname, '..', '..', '.env');
|
|
|
|
function ensureEnvSecret(key, envVarName) {
|
|
if (!process.env[envVarName] || process.env[envVarName].length < 16) {
|
|
const secret = crypto.randomBytes(32).toString('hex');
|
|
let envContent = '';
|
|
if (fs.existsSync(envPath)) {
|
|
envContent = fs.readFileSync(envPath, 'utf8');
|
|
}
|
|
if (envContent.includes(`${envVarName}=`)) {
|
|
envContent = envContent.replace(new RegExp(`${envVarName}=.*`, 'g'), `${envVarName}=${secret}`);
|
|
} else {
|
|
envContent += `\n${envVarName}=${secret}\n`;
|
|
}
|
|
fs.writeFileSync(envPath, envContent);
|
|
process.env[envVarName] = secret;
|
|
dotenv.config({ path: envPath, override: true });
|
|
console.log(`✅ ${envVarName} auto-generated and saved to .env`);
|
|
}
|
|
}
|
|
|
|
function initDatabase(db) {
|
|
const dataDir = path.join(__dirname, '..', '..', 'data');
|
|
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir);
|
|
|
|
db.serialize(() => {
|
|
db.run(`CREATE TABLE IF NOT EXISTS bookings (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
phone TEXT NOT NULL,
|
|
adults INTEGER NOT NULL,
|
|
children INTEGER NOT NULL,
|
|
checkin_date TEXT NOT NULL,
|
|
checkout_date TEXT NOT NULL,
|
|
wishes TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
|
|
const columnsToAdd = [
|
|
'wishes TEXT',
|
|
'status TEXT DEFAULT "новая"',
|
|
'room_type TEXT',
|
|
'room_id INTEGER',
|
|
'comment TEXT',
|
|
'base_price REAL',
|
|
'discount_percent INTEGER DEFAULT 0',
|
|
'discount_amount REAL DEFAULT 0',
|
|
'total_price REAL',
|
|
'promocode_id INTEGER'
|
|
];
|
|
|
|
function addColumnSafely(columns, index) {
|
|
if (index >= columns.length) {
|
|
setupPromocodesTable();
|
|
return;
|
|
}
|
|
db.all("PRAGMA table_info(bookings)", [], (err, cols) => {
|
|
if (err) { setupPromocodesTable(); return; }
|
|
const colNames = cols.map(c => c.name);
|
|
const columnDef = columns[index];
|
|
const colName = columnDef.split(' ')[0];
|
|
if (!colNames.includes(colName)) {
|
|
db.run(`ALTER TABLE bookings ADD COLUMN ${columnDef}`, (err) => {
|
|
if (err && !err.message.includes('duplicate')) {
|
|
console.log('Migration note (ignore if exists):', err.message);
|
|
}
|
|
});
|
|
}
|
|
addColumnSafely(columns, index + 1);
|
|
});
|
|
}
|
|
|
|
function setupPromocodesTable() {
|
|
db.run(`CREATE TABLE IF NOT EXISTS promocodes (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
code TEXT NOT NULL UNIQUE,
|
|
discount_percent INTEGER NOT NULL CHECK(discount_percent BETWEEN 1 AND 99),
|
|
valid_from DATETIME,
|
|
valid_to DATETIME,
|
|
valid_days INTEGER,
|
|
is_active INTEGER DEFAULT 1,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
setupRoomsTable();
|
|
}
|
|
|
|
function setupRoomsTable() {
|
|
db.run(`CREATE TABLE IF NOT EXISTS 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
|
|
)`);
|
|
|
|
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]) => {
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
pending--;
|
|
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`, () => {});
|
|
setupBookingHistoryTable();
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function setupBookingHistoryTable() {
|
|
db.run(`CREATE TABLE IF NOT EXISTS booking_history (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
booking_id INTEGER NOT NULL,
|
|
user_id INTEGER,
|
|
user_login TEXT,
|
|
field TEXT NOT NULL,
|
|
old_value TEXT,
|
|
new_value TEXT,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (booking_id) REFERENCES bookings(id)
|
|
)`);
|
|
setupUsersTable();
|
|
}
|
|
|
|
function setupUsersTable() {
|
|
db.run(`CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
login TEXT NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL,
|
|
full_name TEXT,
|
|
email TEXT,
|
|
role TEXT NOT NULL DEFAULT 'user',
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
setupSettingsTable();
|
|
}
|
|
|
|
function setupSettingsTable() {
|
|
db.run(`CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL,
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
setupReviewsTable();
|
|
}
|
|
|
|
function setupReviewsTable() {
|
|
db.run(`CREATE TABLE IF NOT EXISTS reviews (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
author_name TEXT NOT NULL,
|
|
country TEXT NOT NULL,
|
|
country_code TEXT,
|
|
city TEXT NOT NULL,
|
|
stars REAL NOT NULL CHECK(stars >= 0 AND stars <= 5),
|
|
text TEXT NOT NULL,
|
|
review_code TEXT NOT NULL,
|
|
ip_address TEXT,
|
|
is_approved INTEGER DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`, (err) => {
|
|
if (err && !err.message.includes('already exists')) {
|
|
console.error('Reviews table error:', err.message);
|
|
}
|
|
db.run(`PRAGMA foreign_keys = ON`);
|
|
db.all("PRAGMA table_info(reviews)", [], (err, cols) => {
|
|
if (err || !cols) return;
|
|
const colNames = cols.map(c => c.name);
|
|
if (!colNames.includes('country_code')) {
|
|
db.run("ALTER TABLE reviews ADD COLUMN country_code TEXT", (err) => {
|
|
if (err) console.log('Migration: country_code column:', err.message);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
setupRoomImages();
|
|
}
|
|
|
|
function setupRoomImages() {
|
|
db.run(`CREATE TABLE IF NOT EXISTS room_images (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
room_id INTEGER NOT NULL,
|
|
image_path TEXT NOT NULL,
|
|
sort_order INTEGER DEFAULT 0,
|
|
is_primary INTEGER DEFAULT 0,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
|
|
)`);
|
|
setupSeasonalPrices();
|
|
}
|
|
|
|
function setupSeasonalPrices() {
|
|
db.run(`CREATE TABLE IF NOT EXISTS seasonal_prices (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
room_type TEXT NOT NULL,
|
|
date_from TEXT NOT NULL,
|
|
date_to TEXT NOT NULL,
|
|
price_per_night INTEGER NOT NULL,
|
|
label TEXT,
|
|
is_active INTEGER DEFAULT 1,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
)`);
|
|
setupBackupsTable();
|
|
}
|
|
|
|
function setupBackupsTable() {
|
|
db.run(`CREATE TABLE IF NOT EXISTS backups (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
filename TEXT NOT NULL,
|
|
size INTEGER,
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
type TEXT DEFAULT 'manual',
|
|
restored_at DATETIME
|
|
)`, () => {
|
|
setupIndices();
|
|
});
|
|
}
|
|
|
|
function setupIndices() {
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_bookings_checkin ON bookings(checkin_date)`);
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_bookings_status ON bookings(status)`);
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_bookings_room_id ON bookings(room_id)`);
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_room_images_room_id ON room_images(room_id)`);
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_seasonal_prices_dates ON seasonal_prices(date_from, date_to)`);
|
|
db.run(`CREATE INDEX IF NOT EXISTS idx_seasonal_prices_type ON seasonal_prices(room_type)`);
|
|
console.log('✅ Database initialized');
|
|
}
|
|
|
|
addColumnSafely(columnsToAdd, 0);
|
|
});
|
|
}
|
|
|
|
module.exports = { ensureEnvSecret, initDatabase };
|