603 lines
29 KiB
JavaScript
603 lines
29 KiB
JavaScript
// 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(); |