// test-bd.js - Универсальная проверка и исправление структуры базы данных const sqlite3 = require('sqlite3').verbose(); const path = require('path'); const fs = require('fs'); // Цвета для вывода в консоль const colors = { reset: '\x1b[0m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', magenta: '\x1b[35m', cyan: '\x1b[36m' }; console.log(`${colors.cyan}🔍 ЗАПУСК ПРОВЕРКИ БАЗЫ ДАННЫХ${colors.reset}`); console.log('=' .repeat(60)); // Путь к базе данных const dbPath = path.join(__dirname, 'data', 'school_crm.db'); console.log(`${colors.blue}📁 База данных:${colors.reset} ${dbPath}`); // Проверяем существование файла базы данных if (!fs.existsSync(dbPath)) { console.log(`${colors.yellow}⚠️ Файл базы данных не найден. Он будет создан при первом запуске.${colors.reset}`); } else { const stats = fs.statSync(dbPath); console.log(`${colors.green}✅ Файл базы данных существует (${(stats.size / 1024).toFixed(2)} KB)${colors.reset}`); } // Подключаемся к базе данных const db = new sqlite3.Database(dbPath); // Определяем ожидаемую структуру всех таблиц const expectedTables = { // Основные таблицы пользователей users: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'login', type: 'TEXT UNIQUE NOT NULL' }, { name: 'password', type: 'TEXT' }, { name: 'name', type: 'TEXT NOT NULL' }, { name: 'email', type: 'TEXT UNIQUE NOT NULL' }, { name: 'role', type: 'TEXT DEFAULT "teacher"' }, { name: 'auth_type', type: 'TEXT DEFAULT "local"' }, { name: 'groups', type: 'TEXT' }, { name: 'description', type: 'TEXT' }, { name: 'avatar', type: 'TEXT' }, { name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }, { name: 'last_login', type: 'DATETIME' }, { name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)', 'CREATE INDEX IF NOT EXISTS idx_users_role ON users(role)', 'CREATE INDEX IF NOT EXISTS idx_users_login ON users(login)' ] }, // Таблица задач tasks: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'title', type: 'TEXT NOT NULL' }, { name: 'description', type: 'TEXT' }, { name: 'status', type: 'TEXT DEFAULT "active"' }, { name: 'created_by', type: 'INTEGER NOT NULL' }, { name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }, { name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }, { name: 'deleted_at', type: 'DATETIME' }, { name: 'deleted_by', type: 'INTEGER' }, { name: 'original_task_id', type: 'INTEGER' }, { name: 'start_date', type: 'DATETIME' }, { name: 'due_date', type: 'DATETIME' }, { name: 'rework_comment', type: 'TEXT' }, { name: 'closed_at', type: 'DATETIME' }, { name: 'closed_by', type: 'INTEGER' }, { name: 'task_type', type: 'TEXT DEFAULT "regular"' }, { name: 'type', type: 'TEXT' }, { name: 'approver_group_id', type: 'INTEGER' }, { name: 'document_id', type: 'INTEGER' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_tasks_created_by ON tasks(created_by)', 'CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status)', 'CREATE INDEX IF NOT EXISTS idx_tasks_task_type ON tasks(task_type)', 'CREATE INDEX IF NOT EXISTS idx_tasks_due_date ON tasks(due_date)', 'CREATE INDEX IF NOT EXISTS idx_tasks_closed_at ON tasks(closed_at)', 'CREATE INDEX IF NOT EXISTS idx_tasks_original_task_id ON tasks(original_task_id)' ] }, // Назначения задач task_assignments: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'task_id', type: 'INTEGER NOT NULL' }, { name: 'user_id', type: 'INTEGER NOT NULL' }, { name: 'status', type: 'TEXT DEFAULT "assigned"' }, { name: 'start_date', type: 'DATETIME' }, { name: 'due_date', type: 'DATETIME' }, { name: 'rework_comment', type: 'TEXT' }, { name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }, { name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_task_assignments_task_id ON task_assignments(task_id)', 'CREATE INDEX IF NOT EXISTS idx_task_assignments_user_id ON task_assignments(user_id)', 'CREATE INDEX IF NOT EXISTS idx_task_assignments_status ON task_assignments(status)', 'CREATE UNIQUE INDEX IF NOT EXISTS idx_task_assignments_unique ON task_assignments(task_id, user_id) WHERE status != "deleted"' ] }, // Файлы задач task_files: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'task_id', type: 'INTEGER NOT NULL' }, { name: 'user_id', type: 'INTEGER NOT NULL' }, { name: 'filename', type: 'TEXT NOT NULL' }, { name: 'original_name', type: 'TEXT NOT NULL' }, { name: 'file_path', type: 'TEXT NOT NULL' }, { name: 'file_size', type: 'INTEGER NOT NULL' }, { name: 'uploaded_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_task_files_task_id ON task_files(task_id)', 'CREATE INDEX IF NOT EXISTS idx_task_files_user_id ON task_files(user_id)' ] }, // Логи активности activity_logs: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'task_id', type: 'INTEGER NOT NULL' }, { name: 'user_id', type: 'INTEGER NOT NULL' }, { name: 'action', type: 'TEXT NOT NULL' }, { name: 'details', type: 'TEXT' }, { name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_activity_logs_task_id ON activity_logs(task_id)', 'CREATE INDEX IF NOT EXISTS idx_activity_logs_created_at ON activity_logs(created_at)' ] }, // Группы пользователей user_groups: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'name', type: 'TEXT NOT NULL UNIQUE' }, { name: 'description', type: 'TEXT' }, { name: 'color', type: 'TEXT DEFAULT "#3498db"' }, { name: 'can_approve_documents', type: 'BOOLEAN DEFAULT 0' }, { name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }, { name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_user_groups_name ON user_groups(name)', 'CREATE INDEX IF NOT EXISTS idx_user_groups_can_approve ON user_groups(can_approve_documents)' ] }, // Членство в группах user_group_memberships: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'user_id', type: 'INTEGER NOT NULL' }, { name: 'group_id', type: 'INTEGER NOT NULL' }, { name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_user_group_memberships_user_id ON user_group_memberships(user_id)', 'CREATE INDEX IF NOT EXISTS idx_user_group_memberships_group_id ON user_group_memberships(group_id)', 'CREATE UNIQUE INDEX IF NOT EXISTS idx_user_group_memberships_unique ON user_group_memberships(user_id, group_id)' ] }, // Типы документов (простые) simple_document_types: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'name', type: 'TEXT NOT NULL' }, { name: 'description', type: 'TEXT' }, { name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_simple_document_types_name ON simple_document_types(name)' ] }, // Документы (простые) simple_documents: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'task_id', type: 'INTEGER NOT NULL' }, { name: 'document_type_id', type: 'INTEGER' }, { name: 'document_number', type: 'TEXT' }, { name: 'document_date', type: 'DATE' }, { name: 'pages_count', type: 'INTEGER' }, { name: 'urgency_level', type: 'TEXT CHECK(urgency_level IN ("normal", "urgent", "very_urgent"))' }, { name: 'comment', type: 'TEXT' }, { name: 'refusal_reason', type: 'TEXT' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_simple_documents_task_id ON simple_documents(task_id)', 'CREATE INDEX IF NOT EXISTS idx_simple_documents_document_number ON simple_documents(document_number)' ] }, // Настройки пользователей user_settings: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'user_id', type: 'INTEGER UNIQUE NOT NULL' }, { name: 'email_notifications', type: 'BOOLEAN DEFAULT 1' }, { name: 'notification_email', type: 'TEXT' }, { name: 'telegram_notifications', type: 'BOOLEAN DEFAULT 0' }, { name: 'telegram_chat_id', type: 'TEXT' }, { name: 'vk_notifications', type: 'BOOLEAN DEFAULT 0' }, { name: 'vk_user_id', type: 'TEXT' }, { name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }, { name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_user_settings_user_id ON user_settings(user_id)' ] }, // История уведомлений notification_history: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'user_id', type: 'INTEGER NOT NULL' }, { name: 'task_id', type: 'INTEGER NOT NULL' }, { name: 'notification_type', type: 'TEXT NOT NULL' }, { name: 'last_sent_at', type: 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP' }, { name: 'created_at', type: 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_notification_history_user_id ON notification_history(user_id)', 'CREATE INDEX IF NOT EXISTS idx_notification_history_task_id ON notification_history(task_id)', 'CREATE INDEX IF NOT EXISTS idx_notification_history_type ON notification_history(notification_type)', 'CREATE UNIQUE INDEX IF NOT EXISTS idx_notification_history_unique ON notification_history(user_id, task_id, notification_type)' ] }, // Очередь email email_queue: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'to_email', type: 'TEXT NOT NULL' }, { name: 'subject', type: 'TEXT NOT NULL' }, { name: 'html_content', type: 'TEXT NOT NULL' }, { name: 'user_id', type: 'INTEGER' }, { name: 'task_id', type: 'INTEGER' }, { name: 'notification_type', type: 'TEXT' }, { name: 'retry_count', type: 'INTEGER DEFAULT 0' }, { name: 'status', type: 'TEXT DEFAULT "pending"' }, { name: 'error_message', type: 'TEXT' }, { name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }, { name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' } ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_email_queue_status ON email_queue(status, created_at)', 'CREATE INDEX IF NOT EXISTS idx_email_queue_user_id ON email_queue(user_id)', 'CREATE INDEX IF NOT EXISTS idx_email_queue_task_id ON email_queue(task_id)' ] }, // ===== НОВЫЕ ТАБЛИЦЫ ДЛЯ ЧАТА ===== // Сообщения чата задач task_chat_messages: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'task_id', type: 'INTEGER NOT NULL' }, { name: 'user_id', type: 'INTEGER NOT NULL' }, { name: 'message', type: 'TEXT NOT NULL' }, { name: 'created_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }, { name: 'updated_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' }, { name: 'is_edited', type: 'BOOLEAN DEFAULT 0' }, { name: 'is_deleted', type: 'BOOLEAN DEFAULT 0' }, { name: 'reply_to_id', type: 'INTEGER' } ], foreign_keys: [ 'FOREIGN KEY (task_id) REFERENCES tasks (id) ON DELETE CASCADE', 'FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE', 'FOREIGN KEY (reply_to_id) REFERENCES task_chat_messages (id) ON DELETE SET NULL' ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_task_chat_messages_task_id ON task_chat_messages(task_id)', 'CREATE INDEX IF NOT EXISTS idx_task_chat_messages_user_id ON task_chat_messages(user_id)', 'CREATE INDEX IF NOT EXISTS idx_task_chat_messages_created_at ON task_chat_messages(created_at)', 'CREATE INDEX IF NOT EXISTS idx_task_chat_messages_reply_to ON task_chat_messages(reply_to_id)' ] }, // Файлы в сообщениях чата task_chat_files: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'message_id', type: 'INTEGER NOT NULL' }, { name: 'file_path', type: 'TEXT NOT NULL' }, { name: 'original_name', type: 'TEXT NOT NULL' }, { name: 'file_size', type: 'INTEGER NOT NULL' }, { name: 'file_type', type: 'TEXT' }, { name: 'uploaded_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' } ], foreign_keys: [ 'FOREIGN KEY (message_id) REFERENCES task_chat_messages (id) ON DELETE CASCADE' ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_task_chat_files_message_id ON task_chat_files(message_id)' ] }, // Прочитанные сообщения task_chat_reads: { columns: [ { name: 'id', type: 'INTEGER PRIMARY KEY AUTOINCREMENT' }, { name: 'message_id', type: 'INTEGER NOT NULL' }, { name: 'user_id', type: 'INTEGER NOT NULL' }, { name: 'read_at', type: 'DATETIME DEFAULT CURRENT_TIMESTAMP' } ], foreign_keys: [ 'FOREIGN KEY (message_id) REFERENCES task_chat_messages (id) ON DELETE CASCADE', 'FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE' ], indexes: [ 'CREATE INDEX IF NOT EXISTS idx_task_chat_reads_message_id ON task_chat_reads(message_id)', 'CREATE INDEX IF NOT EXISTS idx_task_chat_reads_user_id ON task_chat_reads(user_id)', 'CREATE UNIQUE INDEX IF NOT EXISTS idx_task_chat_reads_unique ON task_chat_reads(message_id, user_id)' ] } }; // Функция для получения списка существующих таблиц function getExistingTables() { return new Promise((resolve, reject) => { db.all("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name", [], (err, rows) => { if (err) { reject(err); } else { resolve(rows.map(row => row.name)); } }); }); } // Функция для получения структуры таблицы function getTableInfo(tableName) { return new Promise((resolve, reject) => { db.all(`PRAGMA table_info(${tableName})`, [], (err, rows) => { if (err) { reject(err); } else { resolve(rows); } }); }); } // Функция для получения индексов таблицы function getTableIndexes(tableName) { return new Promise((resolve, reject) => { db.all(`PRAGMA index_list(${tableName})`, [], (err, rows) => { if (err) { reject(err); } else { resolve(rows); } }); }); } // Функция для добавления колонки function addColumn(tableName, columnName, columnType) { return new Promise((resolve, reject) => { console.log(`${colors.yellow} ➕ Добавление колонки: ${columnName} (${columnType})${colors.reset}`); db.run(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${columnType}`, function(err) { if (err) { console.log(`${colors.red} ❌ Ошибка: ${err.message}${colors.reset}`); reject(err); } else { console.log(`${colors.green} ✅ Колонка добавлена${colors.reset}`); resolve(); } }); }); } // Функция для создания таблицы function createTable(tableName, tableDefinition) { return new Promise((resolve, reject) => { console.log(`${colors.yellow} 🏗️ Создание таблицы: ${tableName}${colors.reset}`); let createSQL = `CREATE TABLE IF NOT EXISTS ${tableName} (\n`; // Добавляем колонки const columnDefs = tableDefinition.columns.map(col => ` ${col.name} ${col.type}`).join(',\n'); createSQL += columnDefs; // Добавляем внешние ключи, если есть if (tableDefinition.foreign_keys && tableDefinition.foreign_keys.length > 0) { createSQL += ',\n' + tableDefinition.foreign_keys.map(fk => ` ${fk}`).join(',\n'); } createSQL += '\n)'; db.run(createSQL, function(err) { if (err) { console.log(`${colors.red} ❌ Ошибка: ${err.message}${colors.reset}`); reject(err); } else { console.log(`${colors.green} ✅ Таблица создана${colors.reset}`); resolve(); } }); }); } // Функция для создания индекса function createIndex(indexSQL) { return new Promise((resolve, reject) => { db.run(indexSQL, function(err) { if (err) { // Игнорируем ошибки создания индексов, так как они могут уже существовать resolve(); } else { resolve(); } }); }); } // Основная функция проверки async function checkDatabase() { console.log(`${colors.cyan}🔍 Получение списка существующих таблиц...${colors.reset}`); try { const existingTables = await getExistingTables(); console.log(`${colors.green}✅ Найдено таблиц: ${existingTables.length}${colors.reset}`); // Проверяем наличие всех ожидаемых таблиц const expectedTableNames = Object.keys(expectedTables); const missingTables = expectedTableNames.filter(t => !existingTables.includes(t)); const extraTables = existingTables.filter(t => !expectedTableNames.includes(t) && !t.startsWith('sqlite_')); console.log(`\n${colors.cyan}📊 СТАТИСТИКА ТАБЛИЦ:${colors.reset}`); console.log(` Ожидаемых таблиц: ${expectedTableNames.length}`); console.log(` Существующих таблиц: ${existingTables.length}`); console.log(` Отсутствует таблиц: ${missingTables.length}`); console.log(` Лишних таблиц: ${extraTables.length}`); if (extraTables.length > 0) { console.log(`\n${colors.yellow}⚠️ Лишние таблицы (не требуются, но можно оставить):${colors.reset}`); extraTables.forEach(t => console.log(` - ${t}`)); } // Проверяем структуру каждой ожидаемой таблицы console.log(`\n${colors.cyan}🔧 ПРОВЕРКА СТРУКТУРЫ ТАБЛИЦ:${colors.reset}`); for (const tableName of expectedTableNames) { console.log(`\n${colors.magenta}📋 Таблица: ${tableName}${colors.reset}`); const tableDef = expectedTables[tableName]; if (!existingTables.includes(tableName)) { // Таблица не существует - создаём console.log(`${colors.yellow} ⚠️ Таблица не существует${colors.reset}`); await createTable(tableName, tableDef); // Создаём индексы для новой таблицы if (tableDef.indexes && tableDef.indexes.length > 0) { console.log(`${colors.yellow} 🔧 Создание индексов...${colors.reset}`); for (const indexSQL of tableDef.indexes) { await createIndex(indexSQL); } console.log(`${colors.green} ✅ Индексы созданы${colors.reset}`); } continue; } // Таблица существует - проверяем колонки const columns = await getTableInfo(tableName); const existingColumnNames = columns.map(c => c.name.toLowerCase()); console.log(` 📊 Колонок в БД: ${columns.length}, требуется: ${tableDef.columns.length}`); // Проверяем наличие всех необходимых колонок for (const expectedCol of tableDef.columns) { const colName = expectedCol.name.toLowerCase(); if (!existingColumnNames.includes(colName)) { console.log(`${colors.yellow} ⚠️ Отсутствует колонка: ${expectedCol.name}${colors.reset}`); await addColumn(tableName, expectedCol.name, expectedCol.type); } } // Проверяем типы данных колонок (базовая проверка) for (const existingCol of columns) { const expectedCol = tableDef.columns.find(c => c.name.toLowerCase() === existingCol.name.toLowerCase()); if (expectedCol) { const expectedType = expectedCol.type.split(' ')[0].toUpperCase(); const existingType = existingCol.type.toUpperCase(); if (!existingType.includes(expectedType) && !expectedType.includes(existingType)) { console.log(`${colors.yellow} ⚠️ Несоответствие типа: ${existingCol.name} - ожидается ${expectedType}, в БД ${existingType}${colors.reset}`); console.log(` Ручное изменение типа данных может привести к потере данных. Пропускаем.`); } } } // Проверяем индексы try { const indexes = await getTableIndexes(tableName); const existingIndexNames = indexes.map(i => i.name.toLowerCase()); if (tableDef.indexes && tableDef.indexes.length > 0) { console.log(` 🔍 Проверка индексов...`); for (const indexSQL of tableDef.indexes) { // Извлекаем имя индекса из SQL (упрощённо) const match = indexSQL.match(/INDEX\s+IF NOT EXISTS\s+(\w+)/i) || indexSQL.match(/INDEX\s+(\w+)/i); if (match) { const indexName = match[1].toLowerCase(); if (!existingIndexNames.includes(indexName)) { console.log(`${colors.yellow} ➕ Создание индекса: ${indexName}${colors.reset}`); await createIndex(indexSQL); } } } } } catch (err) { console.log(`${colors.red} ❌ Ошибка проверки индексов: ${err.message}${colors.reset}`); } // Проверяем внешние ключи (только для SQLite - ограниченная поддержка) if (tableDef.foreign_keys && tableDef.foreign_keys.length > 0) { // В SQLite сложно проверить внешние ключи через PRAGMA, просто удостоверимся что таблица создана правильно console.log(` 🔍 Внешние ключи определены в структуре таблицы`); } } // Проверяем наличие директорий для файлов console.log(`\n${colors.cyan}📁 ПРОВЕРКА ДИРЕКТОРИЙ:${colors.reset}`); const dirsToCheck = [ path.join(__dirname, 'data', 'uploads'), path.join(__dirname, 'data', 'uploads', 'tasks'), path.join(__dirname, 'data', 'uploads', 'chat'), path.join(__dirname, 'data', 'logs') ]; dirsToCheck.forEach(dir => { if (!fs.existsSync(dir)) { console.log(`${colors.yellow} 📁 Создание директории: ${path.basename(dir)}${colors.reset}`); fs.mkdirSync(dir, { recursive: true }); console.log(`${colors.green} ✅ Создано${colors.reset}`); } else { console.log(`${colors.green} ✅ Директория существует: ${path.basename(dir)}${colors.reset}`); } }); // Итоговый отчёт console.log(`\n${colors.cyan}🏁 ИТОГОВЫЙ ОТЧЁТ:${colors.reset}`); console.log('=' .repeat(60)); // Проверяем все ли таблицы теперь существуют const finalTables = await getExistingTables(); const stillMissing = expectedTableNames.filter(t => !finalTables.includes(t)); if (stillMissing.length === 0) { console.log(`${colors.green}✅ Все необходимые таблицы присутствуют в базе данных.${colors.reset}`); } else { console.log(`${colors.red}❌ Отсутствуют таблицы: ${stillMissing.join(', ')}${colors.reset}`); } console.log(`\n${colors.green}✨ Проверка базы данных завершена!${colors.reset}`); console.log('=' .repeat(60)); } catch (error) { console.error(`${colors.red}❌ Критическая ошибка:${colors.reset}`, error); } finally { // Закрываем соединение с базой данных db.close((err) => { if (err) { console.error(`${colors.red}❌ Ошибка закрытия БД:${colors.reset}`, err.message); } else { console.log(`${colors.green}✅ Соединение с БД закрыто${colors.reset}`); } }); } } // Запускаем проверку checkDatabase();