Files
hotell777_260507/modules/database/index.js
kalugin66 6c4394713c
All checks were successful
Deploy hotel / deploy-kdo (push) Successful in 1m1s
v2
2026-07-18 12:07:29 +05:00

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