Files
hotell777_260507/modules/database/index.js
kalugin66 d539e1f0c0
All checks were successful
Deploy hotel / deploy-kdo (push) Successful in 42s
апи
2026-07-21 20:06:43 +05:00

355 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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',
'remote_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
)`);
setupActivitiesTable();
}
function setupActivitiesTable() {
db.run(`CREATE TABLE IF NOT EXISTS activities (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title_ru TEXT,
title_en TEXT,
title_ab TEXT,
desc_ru TEXT,
desc_en TEXT,
desc_ab TEXT,
icon TEXT,
image_path TEXT,
sort_order INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`, () => {
db.get("SELECT COUNT(*) as count FROM activities", [], (err, row) => {
if (err || row.count > 0) return;
const stmt = db.prepare(`INSERT INTO activities (title_ru, title_en, title_ab, desc_ru, desc_en, desc_ab, icon, image_path, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`);
stmt.run(
'SUP-борды', 'SUP Boards', 'SUP-дкақәа',
'Аренда SUP-бордов прямо на пляже. Исследуйте побережье с воды — идеальный способ увидеть Абхазию с нового ракурса!',
'Rent SUP-boards right on the beach. Explore the coast from the water — the perfect way to see Abkhazia from a new angle!',
'SUP-дкақәа рендара аԥшаҳәаҟны иара убас. Аӡы аҟнытәи аԥшаҳәа аҭҵаара — Аԥсны алахреи ала аҿаԥшра аидеалтә мҩа!',
'fas fa-water', 'img/105d059e5-a204-499f-a01c-c540f6ae1e5d.webp', 1
);
stmt.run(
'Кафе у пляжа', 'Beach Cafe', 'Акафе аԥшаҳәаҟны',
'Всего 2 минуты пешком — уютное кафе с видом на море. Абхазская и русская кухня, свежие морепродукты и ароматный кофе.',
'Just a 2-minute walk — a cozy cafe with a sea view. Abkhazian and Russian cuisine, fresh seafood and aromatic coffee.',
'2 минуҭк мацара ашьааршәара — акафе ҟәышқәа амшын ахсаала ала. Аԥсуеи аурыси акухниа, амшынтәи аԥсыӡқәа ҿыцқәеи акафе иҭагаз.',
'fas fa-coffee', 'img/18abbb136-0e24-4c92-a568-fec8011e029b.webp', 2
);
stmt.run(
'Столовая Hotel 777', 'Hotel 777 Canteen', 'Астоловаиа Hotel 777',
'Домашняя кухня каждый день! Завтраки, обеды и ужины из свежих местных продуктов. Аджика, хачапури, азу и многое другое.',
'Home cooking every day! Breakfast, lunch and dinner made from fresh local ingredients. Adjika, khachapuri, azu and much more.',
'Аҩнытәи акухниа есымша! Ашьажьыкра, аинышәареи ахархәареи аҭыԥантәи аԥсыӡқәа ҿыцқәа ирылхуп. Аџьыка, ахачаԥури, азуи даҽагьы.',
'fas fa-utensils', 'img/1f00325ba-df2c-4bb9-9ec6-28958e11f843.webp', 3
);
stmt.finalize();
console.log('✅ Default activities initialized');
});
});
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
)`, () => {
setupHeroMediaTable();
});
}
function setupHeroMediaTable() {
db.run(`CREATE TABLE IF NOT EXISTS hero_media (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL DEFAULT 'image',
path TEXT NOT NULL,
sort_order INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`, () => {
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)`);
db.run(`DROP INDEX IF EXISTS idx_bookings_remote_id`);
db.run(`CREATE UNIQUE INDEX IF NOT EXISTS idx_bookings_remote_id ON bookings(remote_id)`);
console.log('✅ Database initialized');
}
addColumnSafely(columnsToAdd, 0);
});
}
module.exports = { ensureEnvSecret, initDatabase };