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 };